예제 #1
0
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")
예제 #3
0
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
예제 #4
0
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
예제 #7
0
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
예제 #8
0
    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