This notebook contains the data generation and analysis leading to Figure 2 of the AiiDA-Spirit paper.
In order to use spirit with AiiDA-Spirit make sure that a Code for Spirit's python API is available.
Here our Code is a local python installation which has the spirit module installed: pip3 install numpy spirit
$ verdi code show spirit_pypi@localhost
-------------------- ---------------------------------------------
PK 29784
UUID 2d7ecbb0-3f68-430e-95a6-920b674e33a7
Label spirit_pypi
Description Spirit python API without pinning and defects
Default plugin spirit
Type remote
Remote machine localhost
Remote absolute path /usr/bin/python3
Prepend text
Append text
-------------------- ---------------------------------------------
from aiida import load_profile
load_profile()
from util1 import *
Prepare parameters
parameters = orm.Dict(dict={
# description of the supercell
'n_basis_cells': [50, 50, 1],
# periodicity of the unit cell
'boundary_conditions': [True, True, False],
### External magnetic field vector[T]
'external_field_magnitude': 25.0,
'external_field_normal': [0.0, 0.0, 1.0],
### spin moment
'mu_s': [2.0],
# limit the number of iterations
'llg_n_iterations': 100000
})
Choose run options
run_opt = orm.Dict(
dict={
'simulation_method': 'llg',
'solver': 'vp',
'post_processing': """
# post processing: calculate topological charge
from spirit import quantities
topo_charge = quantities.get_topological_charge(p_state)
with open('topological_charge.txt', 'w') as _f:
_f.write(f'{topo_charge}\\n')
"""
})
Prepare Structure
# simple cubic lattice
structure = orm.StructureData(
cell=[[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0]])
structure.append_atom(position=[0.0, 0.0, 0.0], symbols='Fe')
Prepare Jij/Dij parameters
jijs_expanded = np.array([
#i j da db dc Jij Dx Dy Dz
[0, 0, 1, 0, 0, 10.0, 6.0, 0.0, 0.0],
[0, 0, 0, 1, 0, 10.0, 0.0, 6.0, 0.0],
[0, 0, 0, 0, 1, 10.0, 0.0, 0.0, 6.0],
])
jij_data = orm.ArrayData()
jij_data.set_array('Jij_expanded', jijs_expanded)
Run the SpiritCalculation
inputs = {
'code': orm.Code.get_from_string('spirit_pypi@localhost'),
# 'code': aiida.orm.Code.get_from_string('code-name@computer'),
'structure': structure,
'parameters': parameters,
'run_options': run_opt,
'jij_data': jij_data,
# retrieved the file created in the post-processing step
'add_to_retrieved': orm.List(list=['topological_charge.txt']),
}
# spirit_calc = engine.submit(plugins.CalculationFactory('spirit'), **inputs)
spirit_calc = orm.load_node('7aee0dec-0d3e-4ab7-b633-c2c06da0f5c4')
# Read extracted topological charge
print('Topological charge:', get_topo_charge(spirit_calc))
from aiida_spirit.tools.plotting import init_spinview, show_spins
init_spinview(height_px=800, width_percent=100)
Attention: Wait for the init_spinview to initialize, only then spins can be shown.
show_spins(spirit_calc, show_final_structure=False)
show_spins(spirit_calc, show_final_structure=True)
For varying temperature and external B-field we perform SpiritCalculations and measure the resulting topological charge.
# collect calculations in the group "spirit_Skx_model"
# orm.Group(label='spirit_Skx_model').store()
calculation_group = orm.load_group(label='spirit_Skx_model')
# helper functions for setting up the calculations
def get_parameters(Bz, tempr, llg_seed=20006):
"""get parameters input node"""
parameters = orm.Dict(dict={
# description of the supercell
'n_basis_cells': [50, 50, 1],
# periodicity of the unit cell
'boundary_conditions': [True, True, False],
### External magnetic field vector[T]
'external_field_magnitude': Bz,
'external_field_normal': [0.0, 0.0, 1.0],
# temperature noise [K]
'llg_temperature': temp,
### spin moment
'mu_s': [2.0],
# limit the number of iterations
'llg_n_iterations': 100000,
# seed for random number generator
'llg_seed': llg_seed,
})
return parameters
def get_runopt(iaver = 0):
""" make run options"""
run_opt = orm.Dict(
dict={
'simulation_method': 'llg',
'configuration': {'random': iaver+1},
# Depondt solver to be able to have temperature in the LLG
'solver': 'depondt',
'post_processing': """
# post processing: calculate topological charge
from spirit import quantities
topo_charge = quantities.get_topological_charge(p_state)
with open('topological_charge.txt', 'w') as _f:
_f.write(f'{topo_charge}\\n')
"""
}
)
return run_opt
def get_builder(Bz, temp, iaver):
"""create builder for SpiritCalculation"""
# update the parameters input node
parameters = get_parameters(Bz, temp)
# set run options
run_opt = get_runopt(iaver)
# set of inputs
inputs = {
'code': orm.Code.get_from_string('spirit@iffslurm'),
'structure': structure,
'jij_data': jij_data,
'run_options': run_opt,
'parameters': parameters,
# retrieved the file created in the post-processing step
'add_to_retrieved': orm.List(list=['topological_charge.txt']),
# job settings on cluster
'metadata': {'options': {
# Spirit does not have MPI but by default used OpenMP if that is available
'withmpi': False,
'resources': {'num_machines': 1, 'tot_num_mpiprocs': 1},
# use oscar partition
'queue_name': 'th1',
# max runtime of the spirit job (here: 10h)
'max_wallclock_seconds': 10*3600
},
'label': label,
}
}
# create the process builder
builder = plugins.CalculationFactory('spirit').get_builder()
for k,v in inputs.items():
builder[k] = v
return builder
group_labels = [i.label for i in calculation_group.nodes]
all_calcs = [[temp, Bz] for temp in np.linspace(0, 75, 31) # np.linspace(0, 50, 21)
for Bz in np.linspace(-50, 50, 41)]
submission_counter_tot = 0
# out loop for averaging runs
for iaver in range(5):
builders = []
submission_counter = 0
for temp, Bz in tqdm(all_calcs):
label = f'T={temp}_Bz={Bz}'
if iaver>0:
# add iaver index>0 to label
label += f'_iaver={iaver}'
if label not in group_labels:
builders.append(get_builder(Bz, temp, iaver))
submission_counter += 1
print('submitting calculations:', submission_counter, f'for iaver={iaver}')
submission_counter_tot += submission_counter
# now submit is any jobs are missing
if len(builders)>0:
submit_batch_iffslurm_all_free(builders, dry_run=False, calculations_group=calculation_group, blocking_submit=True,
check_free_nodes = False, allowed_queues=['th1']
)
print('total number of submitted calculations:', submission_counter_tot)
def get_iaver(spirit_calc):
"""get the iaver index"""
if 'iaver' in spirit_calc.label:
return int(spirit_calc.label.split('=')[-1])
else:
return 0
data = []
for i in tqdm(calculation_group.nodes):
# if 'aver' not in i.label and i.is_finished_ok:
if i.is_finished_ok:
tempr, bfield = get_input_para(i)
topo_charge = get_topo_charge(i)
data.append([tempr, bfield, get_iaver(i), topo_charge])
data = np.array(data)
isort = (data[:,2]+1000*data[:,1]+1000000*data[:,0]).argsort()
data = data[isort]
ts = np.sort(list(set(data[:,0])))
bs = np.sort(list(set(data[:,1])))
av = np.sort(list(set(data[:,2])))
nT, nB, nav = len(ts), len(bs), len(av)
plt.figure(figsize=(9,5))
tc = data[:,-1].reshape((nT, nB, nav)).mean(axis=2)
plt.pcolormesh(bs, ts, tc, shading='nearest', cmap='seismic', )#vmin=0) #vmax=0)
plt.colorbar()
cl = plt.gci().get_clim(); cl = max(abs(cl[0]), cl[1]); plt.clim(-cl,cl)
plt.ylabel('Temperature (K)')
plt.xlabel('B field (T)')
ms = 12
ms2 = 14
plt.plot(-40, 30, 'v', color='w', ms=ms, )
plt.plot(-40, 30, 'v', color='k', ms=ms, label='(b)', fillstyle='none')
plt.plot(-20, 10, '<', color='w', ms=ms, )
plt.plot(-20, 10, '<', color='k', ms=ms, label='(c)', fillstyle='none')
plt.plot(0, 0, '*', color='w', ms=ms2,)
plt.plot(0, 0, '*', color='k', ms=ms2, label='(d)', fillstyle='none')
plt.plot(20, 0, 'o', color='w', ms=ms, )
plt.plot(20, 0, 'o', color='k', ms=ms, label='(e)', fillstyle='none')
plt.plot(30, 30, '^', color='w', ms=ms, )
plt.plot(30, 30, '^', color='k', ms=ms, label='(f)', fillstyle='none')
plt.plot(40, 20, 'd', color='w', ms=ms, )
plt.plot(40, 20, 'd', color='k', ms=ms, label='(g)', fillstyle='none')
plt.legend(fontsize='x-large')
plt.xlim(-50.5,50.5)
plt.ylim(-1.2,75.5)
plt.tight_layout()
plt.show()
def get_calc(tempr, bfield, iaver=None):
"""get the calculation from the calculation group by label"""
label = f'T={tempr:.1f}_Bz={bfield:.1f}'
if iaver is not None:
label+= f'_iaver={iaver}'
nodes = [i for i in calculation_group.nodes if i.label==label]
if len(nodes)==1:
return nodes[0]
elif len(nodes)==0:
raise ValueError('No node found!')
else:
raise ValueError('Label not unique!')
calc = get_calc(0,0)
print(calc.label, ':', get_topo_charge(calc))
calc = get_calc(0,20)
print(calc.label, ':', get_topo_charge(calc))
calc = get_calc(20,40)
print(calc.label, ':', get_topo_charge(calc))
calc = get_calc(30,30)
print(calc.label, ':', get_topo_charge(calc))
calc = get_calc(30,-40)
print(calc.label, ':', get_topo_charge(calc))
calc = get_calc(70,40)
print(calc.label, ':', get_topo_charge(calc))
from aiida_spirit.tools.plotting import init_spinview, show_spins
init_spinview(vfr_frame_id='screen', height_px=800)
calc = get_calc(70,40)
show_spins(calc, vfr_frame_id='screen')
calc = get_calc(10,-20)
show_spins(calc, vfr_frame_id='screen')
calc = get_calc(30,-40)
show_spins(calc, vfr_frame_id='screen')
calc = get_calc(0,0)
show_spins(calc, vfr_frame_id='screen')
calc = get_calc(0,20)
show_spins(calc, vfr_frame_id='screen')
calc = get_calc(20,40)
show_spins(calc, vfr_frame_id='screen')
calc = get_calc(30,30)
show_spins(calc, vfr_frame_id='screen')