# 3. Creating and defining a sample#

## 3.1. The sample object#

### 3.1.1. Initialization#

The central object in a TetraX workflow is the sample, represented by an instance of the
`AbstractSample`

class. Much like physical samples, they are
characterized by a geometry, a set of material parameters, a magnetization state and the involved magnetic
interactions. In numerical experiments,
the sample is additionally characterized by an underlying mesh, a scale, a set of differential operators
(gradient, divergence, etc.) tied to the mesh and a number of other attributes necessary for computation. We
can create a sample using

```
>>> sample = tx.create_sample("name of my sample")
```

This will initialize a sample (without a geometry yet) with a set of default material parameters. These parameters can be accessed and set in the usual pythonic way

```
>>> sample.Msat = 800e3 # A/m
>>> sample.alpha = 0.08
>>> print(sample.alpha)
0.08
>>> print(sample.name)
"name of my sample"
```

The name of the sample is set purely for the purpose of storing data in a meaningful way, as all data obtained in
numerical experiments is saved in a directory named after the sample. Note that, as of version 1.3.0, several
basic material parameters such as the saturation magnetization `sample.Msat`

or the exchange-stiffness
constant `sample.Aex`

can be inhomogeneous within the sample (more details below).

### 3.1.2. Setting a geometry#

Before proceeding with anything further, a specific geometry for the sample needs to be set. This is done by supplying a mesh (in meshio format) to the sample using

```
>>> sample.set_geom(mesh)
```

In the `tetrax.geometries`

submodule, we supply a couple of templates to generate common meshes, such as
cuboids, prisms, tubes, wires, and so forth. However, desired meshes can be generated also using pygmsh
or even any external software, as long as the generated mesh files are readable by meshio. Mesh files can be read in
using

```
>>> sample.read_mesh(filename)
```

When setting a mesh/geometry, under the hood, certain preprocessing necessary for later computations is performed. For example, the differential operators on the mesh or the normal vector field of the sample are calculated. After these calculations have been performed, important numerical parameters of the mesh can be accessed, including

`mesh`

, the mesh itself in meshio format,`nx`

, the total number of nodes in the mesh,`nb`

, the number of boundary nodes in the mesh, and`xyz`

, the coordinates at each node as a`MeshVector`

. These coordinates are expressed in units of`scale`

, the characteristic length of the sample. The default is`1e-9`

which means that the coordinates on the mesh are given in nanometers.

Note

The mesh of the sample can visualized using the `show`

method on the sample, to allow for checking
if the mesh was created/obtained according to the request or mesh in mind:

```
>>> sample.show()
```

### 3.1.3. Magnetization vector field#

Once a mesh has been assigned to the sample, the magnetization vector field of the sample (`mag`

) can be set for example
as

```
>>> sample.mag = [0, 0, 1]
```

Note

A magnetization vector field can only be set for samples which already have a mesh.

In antiferromagnetic cases, the sample has a magnetization vector field for each sublattice, `mag1`

and `mag2`

.
To initialize an inhomogeneous magnetization, an array of shape `(nx, 3)`

needs to supplied which can be
seen as a list of triplets with each triplet representing the vector at
a specific node. For convenience, a couple of template vector fields (such as domain walls, vortices, etc.)
can be found in the `tetrax.vectorfields`

submodule. These fields can be called by supplying `xyz`

, the
coordinates on the mesh.

```
>>> sample.mag = tx.vectorfields.bloch_wall(sample.xyz, ...)
```

When setting a magnetization field, the input field is automatically normalized and internally converted
to a `MeshVector`

, which is a data structure that allows to easily access the different components
of the magnetization.

```
>>> sample.mag.x
MeshScalar([0.0, 0.0, 0.12, 0.15, ...])
```

Here, we see that the individual components of a mesh vector are `MeshScalars`

,
another basic data structure in TetraX. We can easily calculate the averages of these objects using the
`sample_average`

function from the `tetrax.helpers.math`

module, which
takes some vector- or scalar field and the sample itself as an input.

```
>>> m = tx.sample_average(sample.mag, sample)
>>> m
[0.98, 0.02, 0.0]
>>> mx = tx.sample_average(sample.mag.x, sample)
>>> mx
0.98
```

Note

The reason, why `sample_average`

is not called `volume_average`

is because not all samples have to be three-dimensional. For example, in waveguide samples, `sample_average`

calculates the average in a cross section.

The mesh and the current magnetization of a sample (if available) can always be visualized using the
`show`

method.

```
>>> sample.show()
```

For further details, see Visualization and evaluation.

## 3.2. Different types of samples#

Depending on the specific type of geometry
and magnetic order, concrete subclasses of `AbstractSample`

can be
generated which are distinguished by their set of possible material parameters (ferromagnetic or antiferromagnetic),
dimensionality, or numerical solvers needed to calculate magnetization dynamics or statics. The type of sample is chosen when creating the sample. At the beginning of this section, a sample was created by
calling `tetrax.create_sample()`

. Per default, this function creates a
ferromagnetic waveguide sample. However, different options are possible.

We can see from the `geometry`

parameter, that different types of samples can be selected:

`confined`

samples such as disks, cuboids, spheres or Möbius ribbons. These types of samples are often the subject of standard three-dimensional micromagnetic simulations. Only in such samples, an LLG solver can be used. Using a confined-wave dynamic-matrix approach, the normal modes of confined samples can be calculated.`waveguide`

samples are infinitely long in one direction (here, the \(z\) direction). Moreover, their (equilibrium) magnetization is taken to be translationally invariant in this direction. Such samples are modelled by discretizing only a single cross section of the waveguide. A propagating-wave dynamic-matrix approach can be used to calculate the normal modes as a function of the wave vector \(k\) along the \(z\) direction.`layer`

samples consist of one or multiple infinitely extended layers which are modelled by discretizing a line trace along the thickness of the layer(s). The (equilibrium) magnetization is taken to be homogeneous within the layer planes. A propagating-wave dynamic-matrix approach can be used to calculate the normal modes as a function of the wave vector \(k\) along the \(z\) direction.

For all three different types of samples, energy minimizers are implemented to calculated the magnetic ground states.

Note

As of version 1.3.0, only ferromagnetic waveguides and multilayers are fully supported, while antiferromagnets and confined samples (without dipolar interaction) are included as experimental features.

## 3.3. Specific geometries#

Template geometries (mesh generators) are all available in the `tetrax.geometries`

module. Even though they are implemented in submodules,
all of them can be accessed in the namespace of `tetrax.geometries`

. For examples

```
sample.set_geom(tx.geometries.tube_cross_section(r=20, R=30, lc=3))
```

Note

For a given sample, only such meshes can be set which fit the type of the sample selected. For example, it is not possible to set a three-dimensional cuboid mesh for a sample which represents the cross section of an infinitely long waveguide.

### 3.3.1. Geometries for confined samples#

The available geometries for three-dimensional `confined`

samples are listed here.

Warning

Three-dimensional confined samples are not fully supported yet. While antiferromagnets are, in principle, fully supported accross all types of geometries, their magnetic tensors are not properly tested yet. Ferromagnetic confined samples do not include dipolar interactions.

### 3.3.2. Geometries for waveguide samples#

### 3.3.3. Geometries for layer samples#

The available geometries for one-dimensional `layer`

samples are listed here.

## 3.4. Material parameters#

Once a sample object is created, being ferro- or antiferromagnetic, it will be initialized with the material parameters of Ni_{80}Fe_{20}(permalloy).
However, the material parameters can be set individually to match the system in study.

### 3.4.1. Ferromagnetic samples#

Typing the following command one can easily check the current material parameters as well as some of the underlaying mesh parameters.

```
>>> sample
```

**Sample: Material parameters**

Material parameters |
Attribute name |
(Average) value |
---|---|---|

Saturation magnetization |
Msat |
796000.0 A/m |

Exch. stiffness constant |
Aex |
1.3e-11 J/m |

Gyromagnetic ratio |
gamma |
176085964400.0 rad Hz/T |

Gilbert damping parameter |
alpha |
0.01 |

Uniaxial anisotropy constant |
Ku1 |
0.0 J/m |

Uniaxial anisotropy direction |
e_u |
array([0,0,1]) |

Cubic anisotropy constant |
Kc1 |
0.0 J/m |

Cubic anisotropy axes |
v1Kc, v2Kc |
(1,0,0), (0,1,0) |

Bulk DMI constant |
Dbulk |
0.0 J/m |

Interfacial DMI constants |
Didmi |
0.0 J/m |

Interlayer exchange constant |
J1 |
0.0 J/m |

**Sample: Mesh parameters**

Mesh parameters |
Attribute name |
Value |
---|---|---|

Mesh scaling |
scale |
1e-9 |

Number of nodes |
nx |
283 |

Number of boundary nodes |
nb |
83 |

Any of the listed parameters can easily be changed using:

```
sample.Msat = 796e3
sample.Aex = 13e-12
sample.alpha = 0.007
sample.scale = 10e-9
```

The uniaxial anisotropy can be defined to be homogeneous over the whole sample or to be spatially varying, both in magnitude and direction. The direction doesn’t need to be a unit vector and will not be normalized in the code. Therefore, the implementation allows to define directions in units of the anisotropy and set the anisotropy constant to 1. For details please check the next sections.

#### 3.4.1.1. Spatial-dependent saturation and exchange stiffness#

Eventhough the attributes `sample.Msat`

and `sample.Aex`

can be set by
specifying a single number, they are internally converted to
`MeshScalars`

of length `sample.nx`

. One
can check this by

```
>>> sample.Msat = 796e3
>>> sample.Msat
MeshScalar([796000., 796000., 796000., 796000., 796000., 796000., 796000.,
.....
796000., 796000., 796000., 796000., 796000., 796000.])
```

or, similarly,

```
>>> sample.Aex
MeshScalar([1.3e-11, 1.3e-11, 1.3e-11, 1.3e-11, 1.3e-11, 1.3e-11, 1.3e-11,
....
1.3e-11, 1.3e-11, 1.3e-11, 1.3e-11, 1.3e-11, 1.3e-11])
```

The average of these scalar fields can always be obtained using

```
>>> sample.average(sample.Aex)
MeshScalar(1.3e-11)
>>> sample.average(sample.Msat)
MeshScalar(796000.)
```

For the saturation, we also supply the shorthand `sample.Msat_avrg`

. Of course,
by supplying the saturation or the exchange stiffness as an array, list or
`MeshScalars`

one can specify inhomogeneous
material parameters and, thereby, different materials. For example, we can create
a tube with a saturation magnetization that is linearly varying along the \(y\)
direction.

```
Mavrg = 1200e3
dMdy = 20e3
sample.mag = [0, 0, 1]
sample.Msat = Mavrg + dMdy * sample.xyz.y
sample.show(show_extrusion=False)
```

Notice that the arrows in the 3D plot are now scaled according to their local value of
`sample.Msat`

(can be deactivated using `show_scaled_mag=False`

). Importantly,
the attribute `sample.mag`

is still a unit vector (\(\mathbf{m}\)) and denotes the local direction of
magnetization!

```
>>> sample.mag
MeshVector([[0., 0., 1.],
[0., 0., 1.],
[0., 0., 1.],
...
```

As result, when initializing a magnetization state, we supply only the unit vector (field) for the direction, as above. The full magnetization \(\mathbf{M}=M_\mathrm{s}\mathbf{m}\) (in units of A/m) can be retrieved using

```
>>> sample.mag_full
MeshVector([[0., 0., 0.12],
[0., 0., 0.12],
[0., 0., 0.125],
...
```

This quantity cannot be set directly. For examples including inhomogeneous exchange and/or saturation magnetization, see ../examples/Py25_CoFeB25_double_layer_Grassi and Magnetization-graded waveguides.

#### 3.4.1.2. Homogeneous uniaxal anisotropy#

A homogeneous uniaxial anisotropy can be set using a triplet for the direction and a single constant. For a single crystal ferromagnetic sample would be like:

```
sample.Ku = 48e3 # in J/m^3
sample.e_u = [1, 0, 0] # along the x axis
```

Easy plane anisotropies can also be defined, by setting the anisotropy constant to negative value. An example for an xy easy plane would be:

```
sample.Ku = -20e3 # in J/m^3
sample.e_u = [0, 0, 1] # along the z axis
```

#### 3.4.1.3. Spatial-dependent uniaxal anisotropy#

A spatial dependent uniaxial anisotropy can be defined by a list of triplets defining the
orientation at each node position and by a list of the constants. The template vectorfields in
`tetrax.vectorfields`

can be used to define spatial dependent vectorfields.

Here is an example for a radial uniaxial anisotropy with a homogeneous anisotropy constant:

```
sample.Ku1 = 2e4 #J/m^3
sample.e_u = tx.vectorfields.radial(sample.xyz,1)
```

The defined vectorfield of the uniaxial anisotropy can be visualized to check if the aimed
spatial dependence has been set or not. This can be done by the `plot()`

method.
For further details please check the provided example: Spatially dependent uniaxial anisotropy.

#### 3.4.1.4. Cubic anisotropy#

A homogeneous cubic anisotropy can be defined by an anisotropy constant Kc1 and two triplets defining the orientation of the easy axes (the third axis will be automatically calculated as the cross product of the other two). The higher order term is not yet included.

Here is an example for a cubic anisotropy with axes along the (100) directions, typical for iron (Fe):

```
sample.Kc1 = 48e3 #J/m^3
sample.v1Kc = (1,0,0)
sample.v2Kc = (0,1,0)
```

For further details please check the provided example: Cubic anisotropy.

#### 3.4.1.5. Interlayer exchange#

Bilinear interlayer-exchange interaction can be used, implemented for layered systems only. The bilinear term can be set using the J1 constant (in J/m^). The constant can also have different values between different layers in a multilayered sample, by supplying a list of values.

Example for bilayers:

```
sample = tx.create_sample(geometry="layer", name="bilayer_iec")
sample.J1 = -3e-4
mesh = tx.geometries.bilayer_line_trace(20,20,2,1)
```

Example for multilayers consisting of 3 magnetic layers and 2 non-magnetic spacers: - antiferromagnetic coupling between the first two layers and ferromagnetic coupling between the second and the third layer

```
sample = tx.create_sample(geometry="layer", name="trilayer_iec")
sample.J1 = [-3e-4,1e-3]
mesh = tx.geometries.multilayer_line_trace([50,20,10],[2,1],[5,5,5])
```

For further details please check the provided example: ../examples/AFM_bilayers.

### 3.4.2. Antiferromagnetic samples#

Warning

As of version 1.3.0 antiferromagnetic samples are implemented, but not fully tested. Therefore, we do not supply any documentation yet. Use on your own risk (and tell us your experiences).