Square ASI robustness to dilution defects#

In this example we generate geometries of dilute square ASI and relax them by a rotating, global field. The results analyzed in terms of vertex type population and their robustness to increasing dilution fraction.

We first create the dilute ensembles from a square ASI. The ensembles are relaxed with a rotating field protocol in flatspin. Finally, we analyze the results with respect to vertex type populations.

Creating dilute ensembles#

First, we generate the example system: a complete square ASI. We will then iteratively remove magnets and save the resulting systems.

from flatspin.model import SquareSpinIceClosed, CustomSpinIce
from flatspin.plotting import plot_vector_image

size = (50, 50)
sq_asi = SquareSpinIceClosed(size = size, lattice_spacing = 1)

plt.title(f'Full original ensemble, size = {size}')

Next, we define the dilution fractions and the corresponding number of magnets to be removed at each dilution fraction.

asi_size_n = sq_asi.spin_count

dilutions = np.array([0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.15, 0.2, 0.3, 0.4])
num_removed = [round(d*asi_size_n) for d in dilutions]

print(f"For {asi_size_n} total magnets")
df = pd.DataFrame({'dilution fraction': dilutions, 'magnets removed': num_removed })
For 5100 total magnets
dilution fraction magnets removed
0 0.001 5
1 0.002 10
2 0.005 26
3 0.010 51
4 0.020 102
5 0.050 255
6 0.100 510
7 0.150 765
8 0.200 1020
9 0.300 1530
10 0.400 2040

We now define the dilute geometries, using several random seeds for the stochastic removal of magnets. The geometries are saved as files where the positions and angles of each spin are stored, to be used by the CustomSpinIce model class.

root_dir = 'dilution'
if not os.path.exists(root_dir):
    print(f"Creating directory {root_dir}")

square_geom = (sq_asi.pos, sq_asi.angle)

random_iterations = 10
for rand_seed in range(random_iterations):
    # Order the ids of the magnets. The order in which they are removed
    removed_id = np.random.choice(asi_size_n, size=max(num_removed), replace=False)

    for i, n in enumerate(num_removed):
        square_geom_removed = (np.delete(square_geom[0], removed_id[:n], axis = 0),
                           np.delete(square_geom[1], removed_id[:n], axis = 0))
        # Save coordinates and angles
        np.savetxt(f'{root_dir}/coords-dilution-{str(i).zfill(2)}-seed-{str(rand_seed).zfill(2)}.csv', (square_geom_removed[0]))
        np.savetxt(f'{root_dir}/angles-dilution-{str(i).zfill(2)}-seed-{str(rand_seed).zfill(2)}.csv', (square_geom_removed[1]))
Creating directory dilution

We can instantiate a CustomSpinIce example model from one of these generated geometries:

dilute_example = CustomSpinIce(magnet_coords = f'{root_dir}/coords-dilution-06-seed-00.csv',
                               magnet_angles = f'{root_dir}/angles-dilution-06-seed-00.csv',
                               radians = True)

plt.title(f'Dilute ensemble, size = {size}, dilution = {1-dilute_example.spin_count/asi_size_n:.2f}')

Baseline field annealing#

We test a field annealing approach for the undiluted system here, before we generate the runs for all diluted systems. See the field annealing documentation for an example of how to set this up.

First, we define some physical parameters of the simulation and a model instance:

# Size of magnets
w, l, t = 220e-9, 80e-9, 20e-9 # [m]
volume = w*l*t # [m^3]
M_sat = 860e3 # [A/m]
M_i = M_sat * volume

a = 1e-9 # lattice_spacing unit [m]
lattice_spacing = 300 # [a]

mu_0 = 1.25663706e-6 # m kg /s^2 A^2
alpha = mu_0 * M_i / (4 * np.pi * a**3)

# Astroid parameters
sw_b = 0.38
sw_c = 1
sw_beta = 1.3
sw_gamma = 3.6

rand_seed = 0
disorder = 0.05
neighbor_distance = 3

test_size = (10,10)

model = SquareSpinIceClosed(size=test_size, lattice_spacing=lattice_spacing, alpha=alpha,
                            disorder=disorder, neighbor_distance=neighbor_distance,
                            sw_b=sw_b, sw_c=sw_c, sw_beta=sw_beta, sw_gamma=sw_gamma,
                            random_seed=rand_seed, use_opencl=1)

Next, we use the Rotate encoder and find some fitting field values that will relax the system with a rotating field. We can adjust the parameter values to see the effect. The current parameters are the ones used for the experiment in the paper, which takes a while to run, even for the smaller test system.

from flatspin.encoder import Rotate

# We define the decreasing field strength
H_hi, H_low = 0.080, 0.072
input_values = np.arange(1,0,-0.001)
timesteps = 100 # (per input value)

# Define the external field values based on the Rotate encoder
encoder = Rotate(H = H_hi, H0 = H_low, timesteps=timesteps)
h_ext = encoder(input_values)

# Start in polarized state

# Record spins and number of spin flips over time
spins = []
flips = []
for i, h in enumerate(h_ext):
    s = model.relax()
    # Record spin state at the end of each rotation
    if (i+1) % timesteps == 0:

print(f"Completed {sum(flips)} flips")
Completed 123917 flips
H = norm(h_ext, axis=-1).round(10)
df = pd.DataFrame({'H': H, 'flips': flips})
df.groupby('H', sort=False).sum().plot(figsize=(10,6))
plt.ylabel("Spin flips")
plt.xlabel("Field strength [T]");

For a too strong (saturating) field strength, all the magnets will be flipped by the field. The maximum number of flips in one rotation of the field, i.e., at one field strength, is twice the total number of magnets (1 flip + 1 flip back). The number of timesteps and the field strength parameters should be tuned so that we start out flipping almost all the magnets (i.e., a strong enough field to change the system state) and go until we don’t flip any magnets as we don’t need to simulate weaker fields than that which results in no activity.

Below, we animate over the states of the relaxation process (skipping quite a few frames).

from matplotlib.animation import FuncAnimation
from IPython.display import HTML

fig, ax = plt.subplots()

skip_frames = 20
def animate_spin(i):
    i = i*skip_frames
    H = norm(h_ext[(i+1) * timesteps - 1])
    ax.set_title(f"H={H:.3f} [T]")
    model.plot_vertex_mag(ax=ax, replace=True)

anim = FuncAnimation(fig, animate_spin, frames=len(spins[::skip_frames]), interval=200, blit=False)
plt.close() # Only show the animation