How to add new properties
Adding new properties is simple but requires integrating your methods for pre- and post-processing correctly. This tutorial guides you in detail on how to do so.
1. Define your property
At first, you need to define and develop methods for the pre- and post-processing.
The method that conducts the pre-processing of your structures should be named generate_structures and takes as input argument only a single ase.atoms object. It needs to return a list of ase.atoms objects modified in the way you wish.
For the post-processing, you need to define a method that takes two arguments: a strucscan.engine.generalengine object and str containing the absolute path to the directory of the final calculation. This method should read the final structures, post-process the data in a custom way and return a python dictionary.
You can take the strucscan.properties.eos module as an example:
[1]:
! cat ../strucscan/properties/eos.py
from ase.eos import EquationOfState
import numpy as np
import os
volume_range = 0.1
num_of_point = 11
def generate_structures(atoms):
"""
:param atoms: (ASE atoms object)
:return: (ASE atoms object list) list of modified ASE atoms objects
"""
vol_min = 1 - volume_range
vol_max = 1 + volume_range
strained_structures = []
for strain in np.linspace(vol_min, vol_max, num_of_point):
basis = atoms.copy()
cell = basis.get_cell()
cell *= strain ** (1. / 3.)
basis.set_cell(cell, scale_atoms=True)
strained_structures.append(basis)
return strained_structures
def get_EOS_properties(calc, absolute_path):
"""
:param calc: (strucscan.engine.generalengine.GeneralEngine object) calculator object
:param absolute_path: (str) absolute path to job directory
:return: (dict) python dictionary with summarized results
"""
energy_list = []
volume_list = []
stress_list = []
pressure_list = []
result_filename = calc.get_result_filename()
for filename in os.listdir(absolute_path):
if (result_filename in filename):
final_struct = calc.read_final_structure(absolute_path,
resultfilename=filename)
energy_list.append(final_struct.get_potential_energy())
volume_list.append(final_struct.get_volume())
stress = final_struct.get_stress()
stress_list.append(stress)
pressure_list.append(-1./3.*sum(stress[0:3]))
eos = EquationOfState(volume_list, energy_list, eos='murnaghan')
v0, e0, B = eos.fit()
outputdict = {}
outputdict["volume"] = np.array(volume_list)
outputdict["energy"] = np.array(energy_list)
outputdict['stresses'] = np.array(stress_list)
outputdict['pressure'] = np.array(pressure_list)
outputdict["equilibrium_energy"] = e0
outputdict["equilibrium_volume"] = v0
outputdict["equilibrium_bulk_modulus"] = B
return outputdict
Respect pre-requesites
If your property requires any pre-requisites, you can link them in the propertis.yaml file in the resource module of strucscan:
[2]:
! cat ../strucscan/resources/properties.yaml
# Each property that requires any kind of pre-processing, e.g.
# generation of multiple input structure files
# are 'advanced' properties and should be listed here.
# The instruction after the colon indicate which property is prerequisite.
# For the properties before the colon, no relaxation will be performed.
# If the property is 'advanced' but requires no prerequisite,
# no conditional property needs to be entered after the colon (= None).
eos: static, atomic, total
dos: total
default_option: atomic
The name of the property that you type before the colon will be the keyword that you hand over to strucscan later in the input files.
2. Integrate your property in the workflow
At last, you need to integrate the call of your property in the workflow. There are two interfaces that need to be respected: ## 1. Pre-processing: This happens in the strucscan.core.jobmaker class. In the method get_advanced_prototypes you are asked to link your property in the if-elif clause as already done for the eos property. Please continue with elif statements and call the generate_structures method of your property module. Make sure you type in the same name of
the property as you have chosen in the properties.yaml.
At the bottom of the strucscan/core/jobmaker.py script you should find:
[3]:
def get_advanced_prototypes(self, jobobject):
property = jobobject.property
basis_ref = jobobject.basis_ref_atoms
if "eos" in property:
from strucscan.properties import eos
strained_structures = eos.generate_structures(basis_ref)
return strained_structures
return
2. Post-processing:
The second interface is the post-processing of the data. This happens when the data is collected which is conducted by the strucscan.core.collector module. Similar to the linking of the pre-processing, you are now asked to integrate your post-processing method. Again, you can take the eos property as an example.
At the bottom of the strucscan/core/collector.py script you should find:
[4]:
def get_result_dict(calc, property, absolute_path):
"""
:param calc: (strucscan.engine.generalengine.GeneralEngine object) calculator object
:param property: (str) name of property
:param absolute_path: (str) absolute path to job directory
:return: (dict) summarized results of calculation
"""
result_dict = {}
try:
if property in ["static", "atomic", "total"]:
from strucscan.properties import bulk
result_dict = bulk.get_bulk_properties(calc, absolute_path)
elif "eos" in property:
from strucscan.properties import eos
result_dict = eos.get_EOS_properties(calc, absolute_path)
except Exception as exception:
if DEBUG():
print("Problem in collecting '{property}' properties from {path}:". \
format(property=property,
path=absolute_path))
exc_type, exc_obj, exc_tb = sys.exc_info()
fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
print("line", exc_tb.tb_lineno, "in", fname, ":", exc_type)
pprint(traceback.format_tb(exc_tb))
print(exception)
return result_dict
And that’s it! You now should be able to run your property as introduced in the second example on the eos property.