Creating and defining a sample ============================== .. contents:: Table of Contents :depth: 2 :local: :backlinks: none The sample object ----------------- Initialization ^^^^^^^^^^^^^^ The central object in a `TetraX` workflow is the sample, represented by an instance of the :class:`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 .. code-block:: python >>> 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 .. code-block:: python >>> 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 :any:`below `). 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 .. code-block:: python >>> sample.set_geom(mesh) In the :mod:`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 .. code-block:: python >>> 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 :class:`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 :meth:`show ` method on the sample, to allow for checking if the mesh was created/obtained according to the request or mesh in mind: .. code-block:: python >>> sample.show() 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 .. code-block:: python >>> 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 :mod:`tetrax.vectorfields` submodule. These fields can be called by supplying ``xyz``, the coordinates on the mesh. .. code-block:: python >>> sample.mag = tx.vectorfields.bloch_wall(sample.xyz, ...) When setting a magnetization field, the input field is automatically normalized and internally converted to a :class:`MeshVector `, which is a data structure that allows to easily access the different components of the magnetization. .. code-block:: python >>> sample.mag.x MeshScalar([0.0, 0.0, 0.12, 0.15, ...]) Here, we see that the individual components of a mesh vector are :class:`MeshScalars `, another basic data structure in `TetraX`. We can easily calculate the averages of these objects using the :func:`sample_average ` function from the :mod:`tetrax.helpers.math` module, which takes some vector- or scalar field and the sample itself as an input. .. code-block:: python >>> 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 :func:`sample_average ` is not called ``volume_average`` is because not all samples have to be three-dimensional. For example, in waveguide samples, :func:`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 :meth:`show ` method. .. code-block:: python >>> sample.show() For further details, see :doc:`/usage/visualization`. Different types of samples -------------------------- Depending on the specific type of geometry and magnetic order, concrete subclasses of :class:`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 :func:`tetrax.create_sample() `. Per default, this function creates a ferromagnetic waveguide sample. However, different options are possible. .. autofunction:: tetrax.core.sample.create_sample 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 :math:`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 :math:`k` along the :math:`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 :math:`k` along the :math:`z` direction. For all three different types of samples, energy minimizers are implemented to calculated the magnetic ground states. .. image:: sample_types.png :width: 800 .. 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. .. _secgeometries: Specific geometries ------------------- Template geometries (mesh generators) are all available in the :mod:`tetrax.geometries` module. Even though they are implemented in submodules, all of them can be accessed in the namespace of :mod:`tetrax.geometries`. For examples .. code-block:: python 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. 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. .. automodule:: tetrax.geometries.geometries3D :members: Geometries for waveguide samples ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. automodule:: tetrax.geometries.geometries2D :members: Geometries for layer samples ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The available geometries for one-dimensional ``layer`` samples are listed here. .. automodule:: tetrax.geometries.geometries1D :members: .. _secmatparam: Material parameters ------------------- Once a sample object is created, being ferro- or antiferromagnetic, it will be initialized with the material parameters of Ni\ :sub:`80`\Fe\ :sub:`20`\ (permalloy). However, the material parameters can be set individually to match the system in study. 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\ :sup:`3` Uniaxial anisotropy direction e_u array([0,0,1]) Cubic anisotropy constant Kc1 0.0 J/m\ :sup:`3` Cubic anisotropy axes v1Kc, (1,0,0), v2Kc (0,1,0) Bulk DMI constant Dbulk 0.0 J/m\ :sup:`2` Interfacial DMI constants Didmi 0.0 J/m\ :sup:`2` Interlayer exchange constant J1 0.0 J/m\ :sup:`2` ============================= ================ ========================= **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: .. code-block:: python 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. 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 :class:`MeshScalars ` of length ``sample.nx``. One can check this by .. code-block:: python >>> sample.Msat = 796e3 >>> sample.Msat MeshScalar([796000., 796000., 796000., 796000., 796000., 796000., 796000., ..... 796000., 796000., 796000., 796000., 796000., 796000.]) or, similarly, .. code-block:: python >>> 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 .. code-block:: python >>> 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 :class:`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 :math:`y` direction. .. code-block:: python Mavrg = 1200e3 dMdy = 20e3 sample.mag = [0, 0, 1] sample.Msat = Mavrg + dMdy * sample.xyz.y sample.show(show_extrusion=False) .. raw:: html 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 (:math:`\mathbf{m}`) and denotes the local direction of magnetization! .. code-block:: python >>> 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 :math:`\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 :doc:`../examples/Py25_CoFeB25_double_layer_Grassi` and :doc:`../examples/magnetization_graded_waveguide`. 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: .. code-block:: python 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: .. code-block:: python sample.Ku = -20e3 # in J/m^3 sample.e_u = [0, 0, 1] # along the z axis 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 :mod:`tetrax.vectorfields` can be used to define spatial dependent vectorfields. Here is an example for a radial uniaxial anisotropy with a homogeneous anisotropy constant: .. code-block:: python 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 :func:`plot() ` method. For further details please check the provided example: :doc:`../examples/round_tube_spatial_dep_anis`. 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): .. code-block:: python sample.Kc1 = 48e3 #J/m^3 sample.v1Kc = (1,0,0) sample.v2Kc = (0,1,0) For further details please check the provided example: :doc:`../examples/monolayer_cubic_anisotropy`. 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: .. code-block:: python 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 .. code-block:: python 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: :doc:`../examples/AFM_bilayers`. 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).