def test_assign_from_structure( clear_database_before_test, db_test_app, ): """ Test using get_pseudos_from_structure """ from aiida_castep.data import get_pseudos_from_structure from aiida.common import NotExistent from ..utils import get_sto_structure db_test_app.upload_otfg_family([Sr_otfg, Ti_otfg, O_otfg], "STO") STO = get_sto_structure() pseudo_list = get_pseudos_from_structure(STO, "STO") assert pseudo_list["Sr"].entry == OTFG_COLLECTION["Sr"] assert pseudo_list["O"].entry == OTFG_COLLECTION["O"] assert pseudo_list["Ti"].entry == OTFG_COLLECTION["Ti"] with pytest.raises(NotExistent): pseudo_list = get_pseudos_from_structure(STO, "STO_O_missing") db_test_app.upload_otfg_family([Sr_otfg, Ti_otfg, O_otfg, "C9"], "STO+C9", stop_if_existing=False) STO.append_atom(symbols=["Ce"], position=[0, 0, 0]) pseudo_list = get_pseudos_from_structure(STO, "STO+C9") assert pseudo_list["Sr"].entry == OTFG_COLLECTION["Sr"] assert pseudo_list["O"].entry == OTFG_COLLECTION["O"] assert pseudo_list["Ti"].entry == OTFG_COLLECTION["Ti"] assert pseudo_list["Ce"].entry == "C9"
def test_assign_from_structure(self): """ Test using get_pseudos_from_structure """ from aiida_castep.data import get_pseudos_from_structure from aiida.common import NotExistent self.upload_usp_family() STO = self.get_STO_structure() pseudo_list = get_pseudos_from_structure(STO, "STO") for kind in STO.kinds: self.assertIn(kind.name, pseudo_list) with self.assertRaises(NotExistent): STO.append_atom(symbols="Ba", position=(1, 1, 1)) pseudo_list = get_pseudos_from_structure(STO, "STO")
def generate_inputs_calculation( protocol: Dict, code: orm.Code, structure: orm.StructureData, otfg_family: OTFGGroup, override: Dict[str, Any] = None ) -> Dict[str, Any]: """Generate the inputs for the `CastepCalculation` for a given code, structure and pseudo potential family. :param protocol: the dictionary with protocol inputs. :param code: the code to use. :param structure: the input structure. :param otfg_family: the pseudo potential family. :param override: a dictionary to override specific inputs. :return: the fully defined input dictionary. """ from aiida_castep.calculations.helper import CastepHelper override = {} if not override else override.get('calc', {}) # This merge perserves the merged `parameters` in the override merged_calc = recursive_merge(protocol['calc'], override) # Create KpointData for CastepCalculation, the kpoints_spacing passed is # already in the AiiDA convention, e.g. with 2pi factor built into it. kpoints = orm.KpointsData() kpoints.set_cell_from_structure(structure) kpoints.set_kpoints_mesh_from_density(protocol['kpoints_spacing']) # For bare calculation level, we need to make sure the dictionary is not "flat" param = merged_calc['parameters'] # Remove incompatible options: cut_off_energy and basis_precisions can not be # both specified if 'cut_off_energy' in param: param.pop('basis_precision', None) helper = CastepHelper() param = helper.check_dict(param, auto_fix=True, allow_flat=True) dictionary = { 'structure': structure, 'kpoints': kpoints, 'code': code, 'parameters': orm.Dict(dict=param), 'pseudos': get_pseudos_from_structure(structure, otfg_family.label), 'metadata': merged_calc.get('metadata', {}) } # Add the settings input if present if 'settings' in merged_calc: dictionary['settings'] = orm.Dict(dict=merged_calc['settings']) return dictionary
def test_using_aiida_pseudo(gen_instance, sto_calc_inputs, dojo_pseudo): """ Test that the inputs generator correctly handles the case with kind.name != symbol """ from aiida_castep.data import get_pseudos_from_structure pps = get_pseudos_from_structure(sto_calc_inputs.structure, dojo_pseudo) assert 'Sr' in pps assert 'Ti' in pps assert 'O' in pps sto_calc_inputs.pseudos = pps gen_instance.inputs = sto_calc_inputs gen_instance.prepare_inputs()
def test_assign_from_structure(self): """ Test using get_pseudos_from_structure """ from aiida_castep.data import get_pseudos_from_structure from aiida.common import NotExistent self.create_family() STO = self.get_STO_structure() pseudo_list = get_pseudos_from_structure(STO, "STO_FULL") self.assertEqual(pseudo_list["Sr"].entry, Sr_otfg) self.assertEqual(pseudo_list["O"].entry, O_otfg) self.assertEqual(pseudo_list["Ti"].entry, Ti_otfg) with self.assertRaises(NotExistent): pseudo_list = get_pseudos_from_structure(STO, "STO_O_missing") pseudo_list = get_pseudos_from_structure(STO, "STO_O_C9") self.assertEqual(pseudo_list["Sr"].entry, Sr_otfg) self.assertEqual(pseudo_list["O"].entry, "C9") self.assertEqual(pseudo_list["Ti"].entry, Ti_otfg)
def generate_inputs_calculation( protocol: Dict, code: orm.Code, structure: orm.StructureData, otfg_family: OTFGGroup, override: Dict[str, Any] = None) -> Dict[str, Any]: """Generate the inputs for the `CastepCalculation` for a given code, structure and pseudo potential family. :param protocol: the dictionary with protocol inputs. :param code: the code to use. :param structure: the input structure. :param otfg_family: the pseudo potential family. :param override: a dictionary to override specific inputs. :return: the fully defined input dictionary. """ from aiida_castep.calculations.helper import CastepHelper merged = recursive_merge(protocol, override or {}) kpoints = orm.KpointsData() kpoints.set_cell_from_structure(structure) kpoints.set_kpoints_mesh_from_density(protocol['kpoints_spacing'] * pi * 2) # For bare calculation level, we need to make sure the dictionary is not "flat" param = merged['calc']['parameters'] helper = CastepHelper() param = helper.check_dict(param, auto_fix=True, allow_flat=True) dictionary = { 'structure': structure, 'kpoints': kpoints, 'code': code, 'parameters': orm.Dict(dict=param), 'pseudos': get_pseudos_from_structure(structure, otfg_family.label), 'metadata': merged.get('metadata', {}) } # NOTE: Need to process settings return dictionary
def use_pseudos_from_family(builder, family_name): """ Set the pseudos port namespace for a builder using pseudo family name :note: The structure must already be set in the builder. :param builder: ProcessBuilder instance to be processed, it must have a structure :param family_name: the name of the group containing the pseudos :returns: The same builder with the pseudopotential set """ from collections import defaultdict from aiida_castep.data import get_pseudos_from_structure # A dict {kind_name: pseudo_object} # But we want to run with use_pseudo(pseudo, kinds) structure = builder.get(INPUT_LINKNAMES['structure'], None) if structure is None: raise RuntimeError('The builder must have a StructureData') kind_pseudo_dict = get_pseudos_from_structure(structure, family_name) for kind, pseudo in kind_pseudo_dict.items(): builder.pseudos.__setattr__(kind, pseudo) return builder
def validate_inputs(self): # pylint: disable=too-many-branches, too-many-statements """Validate the inputs. Populate the inputs in the context This inputs is used as a staging area for the next calculation to be launched""" self.ctx.inputs = AttributeDict({ 'structure': self.inputs.calc.structure, 'code': self.inputs.calc.code, }) input_parameters = self.inputs.calc.parameters.get_dict() # Copy over the metadata self.ctx.inputs['metadata'] = AttributeDict(self.inputs.calc.metadata) # Ensure that the label is carried over to the calculation if not self.ctx.inputs['metadata'].get('label'): self.ctx.inputs['metadata']['label'] = self.inputs.metadata.get( 'label', '') # Set the metadata.options for the underlying CastepCalculation # There are two ways to do this, one can either set it directly under the calc # namespace, or supply a dedicated Dict under 'calc_options' # The latter allows the get_builder_restart to work at the workchain level self.ctx.inputs['metadata']['options'] = AttributeDict( self.inputs.calc.metadata.options) # Check if there is any content if 'resources' in self.inputs.calc.metadata.options: self.report( 'Direct input of calculations metadata is deprecated - please pass them with `calc_options` input port.' ) if self.inputs.get('calc_options'): self.ctx.inputs['metadata']['options'].update( self.inputs['calc_options']) # propagate the settings to the inputs of the CalcJob if 'settings' in self.inputs.calc: self.ctx.inputs.settings = self.inputs.calc.settings.get_dict() else: self.ctx.inputs.settings = {} # Process the options in the input if 'options' in self.inputs: options = self.inputs.options.get_dict() else: options = {} self.ctx.options = options # Deal with the continuations use_bin = options.get('use_castep_bin', False) if use_bin: restart_suffix = 'castep_bin' else: restart_suffix = 'check' # Set the seed name seedname = self.inputs.calc.metadata.options.seedname # In case we are dealing with a plain inputs, extend any plain inputs helper = CastepHelper() param_dict = helper.check_dict(input_parameters) self.ctx.inputs['parameters'] = param_dict if self.inputs.get('continuation_folder'): self.ctx.inputs[INPUT_LINKNAMES[ 'parent_calc_folder']] = self.inputs.continuation_folder self.ctx.inputs.parameters['PARAM'][ 'continuation'] = 'parent/{}.{}'.format( seedname, restart_suffix) self.ctx.inputs.parameters['PARAM'].pop('reuse', None) elif self.inputs.get('reuse_folder'): self.ctx.inputs[INPUT_LINKNAMES[ 'parent_calc_folder']] = self.inputs.reuse_folder self.ctx.inputs.parameters['PARAM'][ 'reuse'] = 'parent/{}.{}'.format(seedname, restart_suffix) self.ctx.inputs.parameters['PARAM'].pop('continuation', None) # Kpoints if self.inputs.calc.get('kpoints'): self.ctx.inputs.kpoints = self.inputs.calc.kpoints elif self.inputs.get('kpoints_spacing'): spacing = self.inputs.kpoints_spacing.value kpoints = orm.KpointsData() # Here the pbc settings of the structure is respected. # If a direction is not periodic it will have a single kpoint for the grid. # Care should be taken if a periodic structure incorrectly set to be # non-periodic! The kpoint along the non-periodic direction will be set to 1 by AiiDA's routine kpoints.set_cell_from_structure(self.inputs.calc.structure) kpoints.set_kpoints_mesh_from_density(np.pi * 2 * spacing) if not all(kpoints.pbc): self.report(( "WARNING: Non-periodic structure detected. Kpoint spacings are set to reflect the non-periodicity." "This only makes sense for molecules-in-a-box input structures." "Bear in mind that plane-wave DFT calculations are always periodic internally." )) # CASTEP uses the original MP grid definition such that only dimensions with odd number # of points are Gamma-centred. # Shifts of the grid is needed to ensure Gamma-centering needs to be enforced. mesh, _ = kpoints.get_kpoints_mesh() use_gamma = self.inputs.get('ensure_gamma_centering') if use_gamma is not None and use_gamma.value is True: castep_offset = _compute_castep_gam_offset(mesh) kpoints.set_kpoints_mesh(mesh, castep_offset) self.report("Offset used for Gamma-centering: {}".format( castep_offset)) self.report("Using kpoints: {}".format(kpoints.get_description())) self.ctx.inputs.kpoints = kpoints else: self.report('No valid kpoint input specified') return self.exit_codes.ERROR_INVALID_INPUTS # Pass extra kpoints exposed_inputs = self.exposed_inputs(CastepCalculation, 'calc') for key, value in exposed_inputs.items(): if key.endswith('_kpoints'): self.ctx.inputs[key] = value # Validate the inputs related to pseudopotentials structure = self.inputs.calc.structure pseudos = self.inputs.calc.get('pseudos', None) pseudos_family = self.inputs.get('pseudos_family', None) if pseudos_family: pseudo_dict = get_pseudos_from_structure(structure, pseudos_family.value) self.ctx.inputs.pseudos = pseudo_dict elif pseudos: self.ctx.inputs.pseudos = pseudos else: self.report('No valid pseudopotential input specified') return self.exit_codes.ERROR_INVALID_INPUTS return None