Neighbor distance#

The dipole field felt by each spin is calculated by summing the dipole field contributions from spins in its neighborhood. The neighbor_distance parameter sets the size of the neighborhood to consider when calculating dipole interactions. Specifically, all spins within a distance of lattice_spacing * neighbor_distance are considered neighbors of a spin. Although it is possible to use neighbor_distance=np.inf for a global neighborhood, this is computationally expensive and (usually) unnecessary, because spins far away have neglible contributions to the total dipole field.

The required neighbor_distance depends on the system under study, i.e., the specific geometry. Care must be taken to include enough spins in the neighborhood such that the observed behavior converges, especially when considering systems exhibiting long-range effects.

In this example, we compare the effect of neighbor_distance on two different systems:

  • Square spin ice, where local interactions are dominating

  • Pinwheel spin ice, where long-range interactions are significant

Here we set the dipolar coupling parameter alpha artificially high to relax each system into a low energy state. Then we investigate how the resulting spin states and vertex populations change as we vary neighbor_distance.

params = {
    'size': (25,25),
    'init': 'random',
    'random_seed': 42,
    'alpha': 1.0,
    'use_opencl': 1,
    'disorder': 0.04,
}
from flatspin.model import SquareSpinIceClosed, PinwheelSpinIceDiamond
from flatspin.plotting import montage_fig

# Preview the two geometries
square = SquareSpinIceClosed(**params)
pinwheel = PinwheelSpinIceDiamond(**params)

plt.figure(figsize=(6, 4))
plt.subplot(121)
plt.axis('off')
plt.title("Square")
square.plot()

plt.subplot(122)
plt.axis('off')
plt.title("Pinwheel")
pinwheel.plot()

plt.tight_layout();
../_images/6a5b2cf0bd357b9a3166ea0a5d0a4800168ab19f1d117f9a01b60c49c164b8fd.svg

Relaxation with high alpha#

Below we plot the vertex magnetization of the initial random state, and after calling relax().

plt.figure(figsize=(6, 6))

plt.subplot(221)
plt.axis('off')
plt.title("Square: init")
square.plot_vertex_mag()

plt.subplot(222)
plt.axis('off')
plt.title("Pinwheel: init")
pinwheel.plot_vertex_mag()

square.relax()
plt.subplot(223)
plt.axis('off')
plt.title("Square: relaxed")
square.plot_vertex_mag()

pinwheel.relax()
plt.subplot(224)
plt.axis('off')
plt.title("Pinwheel: relaxed")
pinwheel.plot_vertex_mag()

plt.tight_layout();
../_images/2fcf0f29d59fa4485060eb585b2d5ad119dd23d7b374bad811255bb4a1ca3525.svg

Effect of neighbor_distance#

The function below performs the above relaxation for a range of neighbor_distance, and records the resulting spin state and vertex population. Note that the initial state of the system is the same, since we use the same random_seed.

def run_neighbor_distance(model_class, max_neighbor_dist=20, **params):
    # Run relax() for different values of neighbor_distance
    spins = []
    vpops = []
    neighbor_dists = np.arange(1, max_neighbor_dist+1)

    for nd in neighbor_dists:
        # neighbor_distance must be set at model initialization time
        si = model_class(neighbor_distance=nd, **params)
        si.relax()

        spins.append(si.spin.copy())

        types, counts = si.vertex_population()
        vpop = [0, 0, 0, 0]
        for vt, c in zip(types, counts):
            vpop[vt-1] = c
        vpops.append(vpop)

    result = {
        'spins': spins,
        'vertex_populations': vpops,
        'neighbor_dists': neighbor_dists,
    }

    return result

Below we define some functions for visualizing the results:

  1. Animation of how the relaxed spin states change as neighbor_distance is varied

  2. Plot vertex population as a function of neighbor_distance

def animate_spins(model, spins, neighbor_dists, **kwargs):
    # Animate list of spin states
    fig, ax = plt.subplots()
    fig.subplots_adjust(left=0, right=1, bottom=0.0, top=.9, wspace=0, hspace=0)

    def animate(i):
        model.set_spin(spins[i])
        ax.cla()
        ax.set_axis_off()
        ax.set_title(f"neighbor_distance={neighbor_dists[i]}")
        model.plot_vertex_mag(ax=ax)

    anim = FuncAnimation(
        fig, animate, init_func=lambda: None,
        frames=len(spins), interval=500, blit=False
    )
    plt.close() # Only show the animation
    return HTML(anim.to_jshtml())

def plot_vertex_populations(spins, neighbor_dists, vertex_populations, **kwargs):
    # Plot vertex populations as function if neighbor_distance
    vpops = np.array(vertex_populations)
    for t in range(4):
        plt.plot(neighbor_dists, vpops[:,t], label=f"Type {t+1}")
    plt.xlabel("Neighbor distance")
    plt.ylabel("Vertex population")
    plt.legend();

Results: square spin ice#

result_square = run_neighbor_distance(SquareSpinIceClosed, max_neighbor_dist=30, **params)

In the animation below, we see visually how the relaxed spin state changes as we vary neighbor_distance. Observe how the state does indeed change over a significant range of neighbor_distance. However, qualitatively the states are fairly similar, e.g., compare the size of the emergent antiferromagnetic domains (white regions).

animate_spins(square, **result_square)