Field-dependent broadband FMR with rectangular waveguide#

In this notebook, we calculate the dynamic susceptibility of the \(k=0\) modes in a rectangular permalloy waveguide with respect to a homogeneous microwave field as a function of a transversally applied static field. The waveguide has a width of 500 nm and a thickness of 20 nm. The resulting spectrum corresponds to what is obtained in a broadband VNA-type FMR experiment.

First, we create the sample and set the material parameters to permalloy.

[1]:
import tetrax as tx
import numpy as np

sample = tx.Sample(
    tx.geometries.waveguide.rectangular(
        width=500,
        thickness=20,
        cell_size_width=5,
        cell_size_thickness=5
    ),
    name="FMR_rect_WG_500nmx20nm"
)


sample.material = tx.materials.permalloy

Dispersion and absorption calculation in a field loop#

For each value of the external field, we relax the system to its equilibrium, calculate the corresponding normal modes at \(k=0\), and subsequently obtain their absorption with respect to a homogeneous microwave field. The resulting susceptibility is stored as a 2D array. Since energy minimization using the relax() function is not the most reliable and tends to fail some points, we use dynamic relaxation using relax_dynamic() in these situations to minimize the total torque in the system instead. Note that, depending on the number of field steps and size of the used mesh, the following cell can take a while (up to one or two hours). This is because so many spectra have to be calculated.

[2]:
sample.antenna = tx.experiments.HomogeneousAntenna(theta=90,phi=90)


static_field_range = np.linspace(0.001, .5 , 150) # in Tesla

susceptibility_map = []
for i, B in enumerate(static_field_range):

    # We reinitialize the sample magnetization at an oblique angle at each field step.
    sample.mag = tx.vectorfields.homogeneous(sample.xyz, 60.0, 0.0)
    sample.external_field = (B, 0, 0)

    # In general, we use energy minimization to calculate the equilibrium state.
    relaxation = tx.experiments.relax(
        sample,
        verbose=False,                  # surpress verbose console output
        set_initial_on_failure=False,    # store last step even on failure
        tolerance=1e-13,
    )
    # Should energy minimization fail to achieve the desired accuracy, we
    # continue with dynamic relaxation (based on the overdamped LLG).
    if not relaxation.was_success:
        print("Relaxing with LLG ...", end="\r")
        tx.experiments.relax_dynamic(
            sample,
            verbose=False,
            tolerance=1e-10
        )

    spectrum = tx.experiments.eigenmodes(sample, k=0, verbose=False)

    # Since we have already set the antenna as part of the sample,
    # we do not need to provide it here again.
    absorb = spectrum.absorption(
        num_f=1000                        # Number of RF frequency points.
    )

    susceptibility_map.append(absorb.susceptibility.flatten())

    print(f"B = {int(B*1e3)} mT ({(i+1)/len(static_field_range)*100:.1f}%) done.", end="\r")
B = 500 mT (100.0%) done. (s), dm/dt = 8297544.445157852 [Hz], <mag.x> = 1.00, <mag.y> = -0.00, <mag.z> = -0.000

The combined relaxation, energy minimization and dynamic LLG, for simpler cases, as this one, can be simplified by using only the energy minimization in a while loop, so that in case the convergence failed, with a small additional noise will try it again. Such, the FMR spectra will be obtained much faster and is always worth starting such. In case the spectra is noisy, use the combined solution and show above.

The code snippet for energy minimization only, is:

success = False
while not success:
    relaxation = tx.experiments.relax(
    sample,
    verbose=False,                  # surpress verbose console output
    set_initial_on_failure=False,    # store last step even on failure
    tolerance=1e-13,
    )
    success = relaxation.was_success

Plotting the results#

We can plot the obtained susceptibility using the plotly package. Note that the dynamic susceptibility is a complex quantity with real and imaginary parts corresponding to microwave dispersion and absorption, respectively. Here, we plot the real part, commonly shown from VNA FMR experiments.

[3]:
from plotly import graph_objects as go

fig = go.Figure()

fig.add_trace(
        go.Heatmap(
            x=static_field_range*1e3,
            y=absorb.microwave_frequency*1e-9,
            z=np.asarray(susceptibility_map).real,
            type="heatmap",
            transpose=True,
            coloraxis="coloraxis"
        ),
    )


fig.update_xaxes(title_text="External field (mT)", exponentformat="power")
fig.update_yaxes(title_text="Frequency (GHz)", exponentformat="power")



fig.update_layout(
        template="simple_white",
        height=600,
        width=600,
        hoverlabel={"bordercolor": "rgba(255,255,255,1)"},
    )

fig.layout.coloraxis.colorbar.title = "Re <i>&#967;</i>"
fig.layout.coloraxis.colorbar.exponentformat = "e"

fig.show(renderer="notebook") # Remove the renderer argument when running on your computer.