Moving simulation window

Moving simulation window#

In this example we move a domain wall in a ferromagnet using a Zhang-Li STT. We let the simulation window move together with the wall, keeping the domain wall centered in the simulation space. Using this, we can virtually simulate an infinitely long magnetic nanowire using a limited number of simulation cells.

Note: The moving window functionality only works properly if - The magnet parameters are uniform - The magnet has no geometry - The magnet has no regions

from matplotlib.animation import FFMpegWriter, FuncAnimation
import matplotlib.pyplot as plt
import numpy as np

from mumaxplus import Ferromagnet, Grid, World
from mumaxplus.util import plot_field, twodomain

# ----------- Material and simulation parameters -----------
cs = 1e-9
length = 256e-9
width = 64e-9
thickness = 1e-9

Ms = 600e3
aex = 10e-12
alpha = 0.02
ku = 6e5
anisU = (1, 0, 0)

# ----------- Create magnet -----------
world = World((cs, cs, cs))
grid = Grid((int(length / cs), int(width / cs), int(thickness / cs)))

magnet = Ferromagnet(world, grid)

magnet.msat  = Ms
magnet.aex   = aex
magnet.alpha = alpha
magnet.ku1   = ku
magnet.anisU = anisU

magnet.enable_demag = False
magnet.enable_openbc = True


# ----------- Create two domain state -----------
magnet.magnetization = twodomain((1, 0, 0), (0, 1, 0), (-1, 0, 0), magnet.center[0], 5e-9)
magnet.minimize()

# ----------- Add current and simulate -----------
magnet.jcur = (-1e12, 0, 0)
magnet.xi   = 0.2
magnet.pol = 1

# Center the simulation window, keeping component 0 (x) close to zero.
# We expect motion along the x axis.
world.center_domain_wall(comp=0, axis=0)

tmax = 0.5e-9
timepoints = np.linspace(0, tmax, 100)

def DW_pos(field):
    mx = field[0, 0, int(width / cs/2), :]
    sign = np.sign(mx)
    # find cell where magnetization component crosses zero
    i = np.where(np.diff(sign) != 0)[0][0]
    # linearly interpolate
    m1, m2 = mx[i], mx[i + 1]
    return (i - m1 / (m2 - m1)) * cs

outputquantities = {"mag": magnet.magnetization,
                    "window position": lambda: world.window.position[0],
                    "wall position": lambda: world.window.position[0] + DW_pos(magnet.magnetization())}

output = world.timesolver.solve(timepoints, outputquantities, tqdm=True)

p0 = output["wall position"][0]

# ----------- Create movie -----------
print("Creating animation...")
fig, axes = plt.subplots(2, 1, figsize=(10, 7))

# Time trace subplot
ax_trace = axes[0]
lines = {}
keys = ["window position", "wall position"]
for key in keys:
    lines[key], = ax_trace.plot([], [], '-', label=key)

ax_trace.set_xlim(0, tmax * 1e9)
ax_trace.set_ylim(min(0, min(output["window position"]) * 1e9),
                  max(0, max(output["wall position"] - p0) * 1e9))
ax_trace.set_xlabel("Time $t$ (ns)")
ax_trace.set_ylabel("position (nm)")
ax_trace.legend()
ax_trace.grid()

# Magnetization image subplot
ax_image = axes[1]
plot_field(output["mag"][0], ax=ax_image, arrow_size=8)

ax_image.set_xlim(0, int(length/cs))
ticks = ax_image.get_xticks()

ax_image.set_title("$t$ = 0.000 ns")
ax_image.set_xlabel("$x$ (nm)")
ax_image.set_ylabel("$y$ (nm)")

fig.tight_layout()

# --- Animation Function ---
def update(frame):
    # Update image
    ax_image.clear()
    plot_field(output["mag"][frame], ax=ax_image, arrow_size=8)
    ax_image.set_xlabel("$x$ (nm)")
    ax_image.set_ylabel("$y$ (nm)")

    # update x-ticks
    shift = output["window position"][frame] * 1e9
    tick_positions = (ticks - shift)
    mask = (0 <= tick_positions) & (tick_positions <= int(length/cs))

    ax_image.set_xticks(tick_positions[mask])
    ax_image.set_xticklabels([f"{t:.0f}" for t in ticks[mask]])
    ax_image.set_title(f"$t$ = {output['time'][frame] * 1e9:.3f} ns")

    # Update time trace
    lines["window position"].set_data(np.array(output["time"][:frame+1]) * 1e9,
                                      np.array(output["window position"][:frame+1]) * 1e9)
    lines["wall position"].set_data(np.array(output["time"][:frame+1]) * 1e9,
                                    np.array(output["wall position"][:frame+1] - p0) * 1e9)

    return [ax_image] + list(lines.values())

# Animation parameters
fps = 15
anim = FuncAnimation(fig, update, frames=len(output["time"]),
                     interval=1000 / fps, repeat_delay=5000 / fps)

# --- Save the animation ---
save_filename = "moving_window.mp4"
writer = FFMpegWriter(fps=fps)
anim.save(save_filename, writer=writer)