Source code for orchestrator.target_property.elastic_constants

from os import path
from ..simulator import simulator_builder
from ..utils import templates
from ..utils.exceptions import AnalysisError
from . import TargetProperty
from orchestrator.target_property.analysis import elastic_compliance
from ..utils.isinstance import isinstance_no_import
# from ..utils.restart import restarter


[docs] class ElasticConstants(TargetProperty): """ Class for determining elastic constants of a material Calculate elastic constants using a variety of atomic displacements. :param target_property_args: dict with the input parameters. The parameters include: lattice_param (float) - lattice parameter in A lattice_type (str) - sc, bcc, fcc, hcp, or diamond [default = sc] deformation_mag (float) - deformation magnitude [default = 1e-4] units (str) - eV/A3 or GPa options [default = eV/A3] simulator_path (str) - path to the lammps executable for use elements (list) - list of element symbols included in the calculation tolerances, iteration maxes? :type target_property_args: dict """
[docs] def __init__(self, **target_property_args): """ Class for determining elastic constants of a material Calculate elastic constants using a variety of atomic displacements. :param target_property_args: dict with the input parameters. The parameters include: lattice_param (float) - lattice parameter in A lattice_type (str) - sc, bcc, fcc, hcp, or diamond [default = sc] deformation_mag (float) - deformation magnitude [default = 1e-4] units (str) - eV/A3 or GPa options [default = eV/A3] simulator_path (str) - path to the lammps executable for use elements (list) - list of element symbols included in the calculation tolerances, iteration maxes? :type target_property_args: dict """ self.lattice_param = target_property_args.get('lattice_param') self.lattice_type = target_property_args.get('lattice_type', 'sc') self.deformation_mag = target_property_args.get( 'deformation_mag', 1e-4) self.valid_lattices = ['sc', 'bcc', 'fcc', 'hcp', 'diamond'] units = target_property_args.get('units', 'eV/A3') if units == 'eV/A3': self.units_cfac = 6.2414e-7 self.units_string = 'eV/A^3' elif units == 'GPa': self.units_cfac = 1.0e-4 self.units_string = 'GPa' else: raise KeyError('units must be set as "eV/A3" or "GPa"') self.job_details = target_property_args.get('job_details', {}) code_path = target_property_args.get('simulator_path') if code_path is None: raise KeyError('simulator_path must be included in the input!') elements = target_property_args.get('elements') if elements is None: raise KeyError('elements must be included in the input!') source_file_location = path.dirname(path.abspath(__file__)) input_template = f'{source_file_location}/templates/in.elastic' simulator_args = { 'code_path': code_path, 'elements': elements, 'input_template': input_template, } self.simulator = simulator_builder.build('LAMMPS', simulator_args) super().__init__(**target_property_args)
[docs] def checkpoint_property(self): """ checkpoint the property module into the checkpoint file save necessary internal variables into a dict with key checkpoint_name and write to the (json) checkpoint file for restart capabilities """ pass
[docs] def restart_property(self): """ restart the property module from the checkpoint file check if the checkpoint_file has an entry matching the checkpoint_name and set internal variables accordingly if so """ pass
[docs] def calculate_property(self, iter_num=0, modified_params=None, potential=None, workflow=None, storage=None, **kwargs): """ Find elastic constants Use a modified version of Aidan Thompson's elastic constant calculation script to compute the elastic properties of a KIM potential :param iter_num: iteration number of the calculation of elastic constants if executed over multiple iterations :type iter_num: int :param modified_params: simulation parameters to modify the initially provided values, including ``lattice_param``, ``lattice_type``, and ``deformation_mag`` :type modified_params: dict :param potential: interatomic potential to be used in LAMMPS. Can be either the string of a KIM potential available via the KIM API or a Potential object created by the Orchestrator. We only support KIM Potentials at this time. :type potential: str or Potential :param workflow: the workflow for managing job submission :type workflow: Workflow :returns: dictionary with the elastic constant tensor as the property_value, None for the property_std, and the calculation id as the calc_ids :rtype: array of floats """ if workflow is None: workflow = self.default_wf # set/update parameters if modified_params is None: modified_params = {} lattice_param = modified_params.get('lattice_param', self.lattice_param) lattice_type = modified_params.get('lattice_type', self.lattice_type) deformation_mag = modified_params.get('deformation_mag', self.deformation_mag) # check parameter bounds assert lattice_param > 0.0, 'lattice parameter must be > 0 A' assert lattice_type in self.valid_lattices, \ f'lattice type must be one of {self.valid_lattices}' assert deformation_mag > 0.0, 'deformation magnitude must be > 0' # get potential from path, or name or module if isinstance_no_import(potential, 'Potential'): if hasattr(potential, 'install_potential_in_kim_api') and callable( potential.install_potential_in_kim_api): module_name = self.__class__.__name__ save_root = workflow.make_path( module_name, 'potential_for_elastic_constants', ) potential_name = potential.kim_id potential.install_potential_in_kim_api( save_path=path.join(save_root, str(iter_num)), import_into_kimkit=False) model_path = f'{save_root}/{iter_num}/{potential_name}' else: raise NotImplementedError('Elastic constants only supports ' 'KIM potentials at this time') else: potential_name = potential # potential is available by name in LAMMPS model_path = None self.potential_name = potential_name sim_params = { 'lattice_param': lattice_param, 'lattice_type': lattice_type, 'deformation_mag': deformation_mag, 'potential': potential_name, 'model_path': model_path, 'units_cfac': self.units_cfac, 'units_string': self.units_string, } # run calculation calc_id = self.conduct_sim(sim_params, workflow, f'elastic_constant/{iter_num}') # wait for completion workflow.block_until_completed(calc_id) # analyze output calc_path = workflow.get_job_path(calc_id) c, s = elastic_compliance(f'{calc_path}/lammps.out') # error checking output? # do something with S matrix? if s is None: raise AnalysisError # return results results_dict = { 'property_value': c, 'property_std': None, 'calc_ids': (calc_id, ), 'success': True, } return results_dict
[docs] def conduct_sim(self, sim_params, workflow, sim_path): """ Perform simulations for the target property calculations This function finalizes the simulator run conditions. Since the calc requires the generation of multiple files prior to running, we also set the Simulator's ``external_setup`` flag to True and provide a function for generating these files. :param sim_params: parameters that define the Simulator run, including the details that are set in the input script as well as the path to the model that should be used :type sim_params: dict :param workflow: the workflow for managing job submission :type workflow: Workflow :param sim_path: path name to specify these calculations :type sim_path: str :returns: calculation ID :rtype: int """ self.simulator.external_setup = True self.simulator.external_func = self._write_potential_modfile # enforce the input file name job_details = {'lammps_input': 'in.elastic'} | self.job_details calc_id = self.simulator.run( sim_path, sim_params['model_path'], sim_params, {'make_config': False}, workflow=workflow, job_details=job_details, ) return calc_id
def _write_potential_modfile(self, output_path): """ utility function to generate additional input files for the calculation The elastic constant script uses two additional input files, potential.mod and displace.mod for the calculation. These are generated by this function. :param output_path: path where the calculation is to be run, and where the files should be written :type output_path: str """ element_list = ' '.join(self.simulator.elements) string = (f'kim interactions {element_list}\n' 'neighbor 1.0 nsq\n' 'neigh_modify once no every 1 delay 0 check yes\n' 'min_style cg\n' 'min_modify dmax ${dmax} line quadratic\n' 'thermo 1\n' 'thermo_style custom step temp pe press pxx pyy pzz pxy pxz ' 'pyz lx ly lz vol\n' 'thermo_modify norm no\n') with open(f'{output_path}/potential.mod', 'w') as fout: fout.write(string) source_file_location = path.dirname(path.abspath(__file__)) displace_template = f'{source_file_location}/templates/displace.mod' displace_file = templates.Templates(displace_template, output_path) _ = displace_file.replace(['potential'], [self.potential_name])
[docs] def calculate_with_error(self, n_calc, modified_params=None, potential=None, workflow=None): pass
[docs] def save_configurations(self, calc_ids, dataset_handle, workflow, storage): pass