[1]:
import holoviews as hv

hv.extension("bokeh", logo=False)
hv.output(widget_location="bottom")

Interactive plots#

This notebook show how to use Holoviews based plotting methods to create interactive plots of the magnetisation or derived quantities. The interface is the same as in discretisedfield.Field.hv. For details on the different plotting methods refer to the documentation of Holoviews based plotting in discretisedfield.

[2]:
import os

import discretisedfield.tools as dft
import numpy as np

import micromagneticdata as md

We have a set of example simulations stored in the test directory of micromagneticdata that we use to demonstrate its functionality.

[3]:
dirname = os.path.join("..", "micromagneticdata", "tests", "test_sample")

Visualising computed magnetisation data#

First, we creata a Data object. We need to pass the name of the micromagneticmodel.System that we used to run the simulation and optionally an additional path to the base directory.

[4]:
data = md.Data(name="rectangle", dirname=dirname)

The Data object contains all simulation runs of the System. These are called drives.

[5]:
data.info
[5]:
drive_number date time driver adapter t n n_threads
0 0 2024-06-11 14:14:32 TimeDriver oommfc 2.500000e-11 25.0 NaN
1 1 2024-06-11 14:14:39 TimeDriver mumax3c 1.500000e-11 15.0 NaN
2 2 2024-06-11 14:14:39 TimeDriver mumax3c 5.000000e-10 250.0 NaN
3 3 2024-06-11 14:14:41 RelaxDriver mumax3c NaN NaN NaN
4 4 2024-06-11 14:14:42 MinDriver mumax3c NaN NaN NaN
5 5 2024-06-11 14:14:42 TimeDriver oommfc 5.000000e-12 5.0 NaN
6 6 2024-06-11 14:14:49 MinDriver oommfc NaN NaN NaN

Time evolution of the magnetisation#

[6]:
time_drive = data[1]

We first create a plot for the averaged magnetisation components to better understand the behaviour of the system. We can see a precession of all three components of the averaged magnetisation.

[7]:
time_drive.table.mpl(y=["mx", "my", "mz"])
../../../_images/documentation_notebooks_micromagneticdata_interactive_plotting_12_0.png

We can use the .hv convenience method to create a plot similar to the plotting functionality in discretisedfield. We get an additional slider for the time variable.

We manually set the colorlimit to (-Ms, Ms) and use a different colormap.

[8]:
time_drive.hv(
    kdims=["x", "y"],
    scalar_kw={"clim": (-800000, 800000), "cmap": "coolwarm"},
)
[8]:

We can see that the magnetisation direction per time-step is uniform throughout the sample.

[9]:
time_drive.hv(
    kdims=["y", "z"],
    scalar_kw={"clim": (-800000, 800000), "cmap": "cividis"},
)
[9]:

Resampling can be done like in discretisedfield using a tuple (to only affect kdims)

[10]:
time_drive.hv(
    kdims=["x", "y"],
    scalar_kw={"clim": (-800000, 800000), "cmap": "coolwarm"},
    vector_kw={"n": (10, 6)},
)
[10]:

Combining multiple drives#

We can combine multiple consecutive time drives to get a single plot showing the whole simulation.

[11]:
full_time_drive = data[0] << data[1] << data[2]

Again, we first show the scalar plot to better understand the data.

[12]:
full_time_drive.table.mpl(y=["mx", "my", "mz"])
../../../_images/documentation_notebooks_micromagneticdata_interactive_plotting_22_0.png

Now, we can create an interactive plot. Note that the resulution of the time slider changes as we “jump” from the first two drives with fine resolution to the last one with a coarser resolution.

[13]:
full_time_drive.hv(
    kdims=["x", "y"],
    scalar_kw={"clim": (-800000, 800000), "cmap": "coolwarm"},
    vector_kw={"n": (10, 5)},
)
[13]:

Computing derived quantities#

In this section we look at a different example simulation, the dynamics of a magnetic vortex. We demonstrate how to compute derived quantities based on a series of magnetisation samples from the time drive.

[14]:
data = md.Data(name="vortex_dynamics", dirname=dirname)
[15]:
data.info
[15]:
drive_number date time driver adapter n_threads t n
0 0 2024-06-11 14:14:56 MinDriver oommfc None NaN NaN
1 1 2024-06-11 14:15:02 MinDriver oommfc None NaN NaN
2 2 2024-06-11 14:15:09 TimeDriver oommfc None 5.000000e-09 250.0

Time evolution of the magnetisation#

[16]:
time_drive = data[2]

We first create a plot for the averaged magnetisation components to better understand the behaviour of the system. We can see a damped precession in x and y.

[17]:
time_drive.table.mpl(y=["mx", "my"])
../../../_images/documentation_notebooks_micromagneticdata_interactive_plotting_31_0.png

We can also create an interactive plot for the full magnetisation.

[18]:
time_drive.hv(kdims=["x", "y"])
[18]:

Selecting only a part of the drive#

We can use slicing notation to select and plot only a part of the data. Here, we select the every 5th of the first 100 samples.

[19]:
time_drive[:100:5].hv(kdims=["x", "y"])
[19]:

Normalised magnetisation#

To plot the normalised field, we can register a callback function. This function will be applied to each field in the drive before the field is passed to the plotting method. The input argument to this function is a discretisedfield.Field. Here, we have a simple function that computes the normalised field (field.orientation). As the code for this function is very short, we can use a lambda function and define it directly inside the register_callback method. The register_callback method returns a new drive object that we can plot.

If you are not familiar with lambda functions you can also use normal functions as demonstrated in one of the next examples.

[20]:
time_drive.register_callback(lambda field: field.orientation).hv(kdims=["x", "y"])
[20]:

Topological charge density - example 1#

To give a second example, we compute the topological charge density in the xy plane. This operation is implemented in discretisedfield.tools. However, discretisedfield.tools.topological_charge_density can only work on a single plane. Therefore, we first select one plane in the z direction and afterwards compute the topological charge density.

Here, we first create a new object and afterwards plot it.

[21]:
time_drive_tcd_1 = time_drive.register_callback(
    lambda field: dft.topological_charge_density(field.sel("z"))
)
[22]:
time_drive_tcd_1.hv.scalar(kdims=["x", "y"], cmap="plasma")
[22]:

Topological charge density - example 2#

If multiple callbacks are registered, these will be applied one after another in the order that they have been registered. Do demonstrate this, we repeat the example of the topological charge density in two steps:

  1. We select a xy single plane

  2. We compute the topological charge density on that plane

Furthermore, we define our functions outside the register_callback method in this example.

[23]:
def select_plane(field):
    return field.sel("z")


def topological_charge_density(field):
    return dft.topological_charge_density(field)


time_drive_tcd_2 = time_drive.register_callback(select_plane)
time_drive_tcd_2 = time_drive_tcd_2.register_callback(topological_charge_density)
time_drive_tcd_2.hv.scalar(kdims=["x", "y"], cmap="plasma")
[23]:

It is possible to display all callbacks, as follows. Note however, that this information is difficult to read when using lambda funtions as their names are not very explanatory.

[24]:
time_drive_tcd_2.callbacks
[24]:
[<function __main__.select_plane(field)>,
 <function __main__.topological_charge_density(field)>]

Hysteresis simulation#

TODO

[25]:
data = md.Data(name="hysteresis", dirname=dirname)
[26]:
data.info
[26]:
drive_number date time driver adapter Hsteps n_threads
0 0 2024-06-11 14:15:23 HysteresisDriver oommfc [[[0, 0, -795774.7154594767], [0, 0, 795774.71... None

Time evolution of the magnetisation#

[27]:
drive = data[0]

We first create a plot for the averaged magnetisation components to better understand the behaviour of the system. We can see a damped precession in x and y.

[28]:
drive.table.mpl(x="Bz_hysteresis", y=["mz"])
../../../_images/documentation_notebooks_micromagneticdata_interactive_plotting_51_0.png

We can also create an interactive plot for the full magnetisation.

Note: we have to change the independent variable drive.x to the iteration (stage) to be able to see the full loop. The default value (B_hysteresis) is not unique.

[29]:
drive.x = "stage"
[30]:
Ms = np.max(np.abs(drive.m0.norm.array))

drive.register_callback(lambda field: field.z).hv(
    kdims=["x", "z"], scalar_kw={"clim": (-Ms, Ms)}
)
[30]: