コード例 #1
0
    def build_output_kpoints(self, parsed_parameters, structure):
        """Build the output kpoints from the raw parsed data.

        :param parsed_parameters: the raw parsed data
        :return: a `KpointsData` or None
        """
        k_points_list = parsed_parameters.pop('k_points', None)
        k_points_units = parsed_parameters.pop('k_points_units', None)
        k_points_weights_list = parsed_parameters.pop('k_points_weights', None)

        if k_points_list is None or k_points_weights_list is None:
            return None

        if k_points_units != '1 / angstrom':
            self.logger.error('Error in kpoints units (should be cartesian)')
            self.exit_code_parser = self.exit_codes.ERROR_INVALID_KPOINT_UNITS
            return None

        kpoints = orm.KpointsData()
        kpoints.set_cell_from_structure(structure)
        kpoints.set_kpoints(k_points_list,
                            cartesian=True,
                            weights=k_points_weights_list)

        return kpoints
コード例 #2
0
def generate_inputs_default():
    """Return only those inputs that the parser will expect to be there."""
    a = 5.43
    structure = orm.StructureData(
        cell=[[a / 2., a / 2., 0], [a / 2., 0, a / 2.], [0, a / 2., a / 2.]])
    structure.append_atom(position=(0., 0., 0.), symbols='Si', name='Si1')
    structure.append_atom(position=(a / 4., a / 4., a / 4.),
                          symbols='Si',
                          name='Si2')
    structure.store()
    parameters = {
        'CONTROL': {
            'calculation': 'scf'
        },
        'SYSTEM': {
            'ecutrho': 240.0,
            'ecutwfc': 30.0
        }
    }
    kpoints = orm.KpointsData()
    kpoints.set_cell_from_structure(structure)
    kpoints.set_kpoints_mesh_from_density(0.15)

    return AttributeDict({
        'structure': structure,
        'kpoints': kpoints,
        'parameters': orm.Dict(dict=parameters),
        'settings': orm.Dict()
    })
コード例 #3
0
def populate_restapi_database(clear_database_before_test):
    """Populates the database with a considerable set of nodes to test the restAPI"""
    # pylint: disable=unused-argument
    from aiida import orm

    struct_forcif = orm.StructureData().store()
    orm.StructureData().store()
    orm.StructureData().store()

    orm.Dict().store()
    orm.Dict().store()

    orm.CifData(ase=struct_forcif.get_ase()).store()

    orm.KpointsData().store()

    orm.FolderData().store()

    orm.CalcFunctionNode().store()
    orm.CalcJobNode().store()
    orm.CalcJobNode().store()

    orm.WorkFunctionNode().store()
    orm.WorkFunctionNode().store()
    orm.WorkChainNode().store()
コード例 #4
0
def test_ph_qpoint_list(
    aiida_profile, fixture_localhost, fixture_sandbox, generate_calc_job, fixture_code, generate_structure,
    generate_kpoints_mesh, generate_remote_data, file_regression
):
    """Test a `PhCalculation` with a qpoint list instead of a mesh."""
    entry_point_name = 'quantumespresso.ph'
    parent_entry_point = 'quantumespresso.pw'
    remote_path = fixture_sandbox.abspath

    structure = generate_structure()
    kpoints = generate_kpoints_mesh(2).get_kpoints_mesh(print_list=True)
    qpoints = orm.KpointsData()
    qpoints.set_cell(structure.cell)
    qpoints.set_kpoints(kpoints)

    inputs = {
        'code': fixture_code(entry_point_name),
        'parent_folder': generate_remote_data(fixture_localhost, remote_path, parent_entry_point),
        'qpoints': qpoints,
        'parameters': orm.Dict(dict={'INPUTPH': {}}),
        'metadata': {
            'options': get_default_options()
        }
    }

    generate_calc_job(fixture_sandbox, entry_point_name, inputs)

    with fixture_sandbox.open('aiida.in') as handle:
        input_written = handle.read()

    file_regression.check(input_written, encoding='utf-8', extension='.in')
コード例 #5
0
def generate_inputs_base(protocol: Dict,
                         code: orm.Code,
                         structure: StructureData,
                         override: Dict[str, Any] = None) -> Dict[str, Any]:
    """Generate the inputs for the `AbinitBaseWorkChain` 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 override: a dictionary to override specific inputs.
    :return: the fully defined input dictionary.
    """
    protocol['abinit'] = generate_inputs_calculation(
        protocol['abinit'], code, structure, override.get('abinit', {}))
    merged = recursive_merge(protocol, override or {})

    if isinstance(merged['abinit']['parameters'], dict):
        merged['abinit']['parameters'] = orm.Dict(
            dict=merged['abinit']['parameters'])

    if merged.get('kpoints_distance') is not None:
        dictionary = {
            'abinit': merged['abinit'],
            'kpoints_distance': orm.Float(merged['kpoints_distance'])
        }
    elif merged.get('kpoints') is not None:
        kpoints = orm.KpointsData()
        kpoints.set_kpoints_mesh(merged['kpoints'])
        dictionary = {'abinit': merged['abinit'], 'kpoints': kpoints}
    else:
        raise ValueError(
            'Neither `kpoints_distance` nor `kpoints` were specified as inputs'
        )

    return dictionary
コード例 #6
0
def test_vasp_hf_wannier_input(
        configure_with_daemon,  # pylint: disable=unused-argument
        assert_finished,
        get_insb_input  # pylint: disable=redefined-outer-name
):
    """
    Runs the workflow that calculates Wannier90 inputs from VASP + hybrids on InSb with a coarse grid.
    """
    from aiida import orm
    from aiida.engine import run_get_node
    from aiida_tbextraction.fp_run.wannier_input import VaspWannierInput

    kpoints_mesh = orm.KpointsData()
    kpoints_mesh.set_kpoints_mesh([2, 2, 2])

    wannier_projections = orm.List()
    wannier_projections.extend(['In : s; px; py; pz', 'Sb : px; py; pz'])

    result, node = run_get_node(
        VaspWannierInput,
        kpoints_mesh=kpoints_mesh,
        wannier_parameters=orm.Dict(
            dict=dict(num_wann=14, num_bands=36, spinors=True)),
        wannier_projections=wannier_projections,
        **get_insb_input)
    assert node.is_finished_ok
    assert all(key in result for key in
               ['wannier_input_folder', 'wannier_parameters', 'wannier_bands'])
    folder_list = result['wannier_input_folder'].get_folder_list()
    assert all(
        filename in folder_list
        for filename in ['wannier90.amn', 'wannier90.mmn', 'wannier90.eig'])
コード例 #7
0
def create_kpoints_from_distance(structure: orm.StructureData,
                                 distance: orm.Float) -> orm.KpointsData:
    """Generate a uniformly spaced kpoint mesh for a given structure.

    The spacing between kpoints in reciprocal space is guaranteed to be at least the defined distance.

    :param structure: the StructureData to which the mesh should apply
    :param distance: a Float with the desired distance between kpoints in reciprocal space
    :returns: a KpointsData with the generated mesh
    """
    epsilon = 1E-5

    kpoints = orm.KpointsData()
    kpoints.set_cell_from_structure(structure)
    kpoints.set_kpoints_mesh_from_density(distance.value)

    lengths_vector = [np.linalg.norm(vector) for vector in structure.cell]
    lengths_kpoint = kpoints.get_kpoints_mesh()[0]

    is_symmetric_cell = all(
        abs(length - lengths_vector[0]) < epsilon for length in lengths_vector)
    is_symmetric_mesh = all(length == lengths_kpoint[0]
                            for length in lengths_kpoint)

    # If the vectors of the cell all have the same length, the kpoint mesh should be isotropic as well
    if is_symmetric_cell and not is_symmetric_mesh:
        nkpoints = max(lengths_kpoint)
        kpoints.set_kpoints_mesh([nkpoints, nkpoints, nkpoints])

    return kpoints
コード例 #8
0
def test_siesta_bands_error(aiida_profile, fixture_localhost, generate_calc_job_node,
    generate_parser, generate_structure, data_regression):
    """
    Test parsing of bands in a situation when the bands file is truncated.
    Also the time.json and MESSAGES file are added, therefore their parsing is tested as well. The MESSAGES
    file is the standard containing only "INFO: Job completed". 
    """

    name = 'bands_error'
    entry_point_calc_job = 'siesta.siesta'
    entry_point_parser = 'siesta.parser'

    structure=generate_structure()
    bandskpoints = orm.KpointsData()
    kpp = [(0.500,  0.250, 0.750), (0.500,  0.500, 0.500), (0., 0., 0.)]
    bandskpoints.set_cell(structure.cell, structure.pbc)
    bandskpoints.set_kpoints(kpp)

    inputs = AttributeDict({
        'structure': structure,
        'bandskpoints': bandskpoints
    })

    attributes=AttributeDict({'input_filename':'aiida.fdf', 'output_filename':'aiida.out', 'prefix':'aiida'})

    node = generate_calc_job_node(entry_point_calc_job, fixture_localhost, name, inputs, attributes)
    parser = generate_parser(entry_point_parser)
    results, calcfunction = parser.parse_from_node(node, store_provenance=False)

    assert calcfunction.is_finished
    assert calcfunction.exception is None
    assert not calcfunction.is_finished_ok
    assert calcfunction.exit_message == 'Failure while parsing the bands file'
    assert 'output_parameters' in results
    assert 'output_structure' in results
コード例 #9
0
def generate_inputs():
    """Return only those inputs that the parser will expect to be there."""
    kpoints = orm.KpointsData()
    kpoints.set_kpoints_mesh([1, 1, 1])

    return AttributeDict({
        'kpoints': kpoints,
    })
コード例 #10
0
ファイル: test_data.py プロジェクト: sponce24/aiida-core
    def generate_class_instance(data_class):
        """Generate a dummy `Data` instance for the given sub class."""
        dirpath_fixtures = os.path.abspath(
            os.path.join(os.path.dirname(__file__), os.pardir, os.pardir,
                         'fixtures'))
        if data_class is orm.CifData:
            instance = data_class(
                file=os.path.join(dirpath_fixtures, 'data', 'Si.cif'))
            return instance

        if data_class is orm.UpfData:
            filename = os.path.join(
                dirpath_fixtures, 'pseudos',
                'Ba.pbesol-spn-rrkjus_psl.0.2.3-tot-pslib030.UPF')
            instance = data_class(file=filename)
            return instance

        if data_class is orm.StructureData:
            instance = orm.CifData(file=os.path.join(
                dirpath_fixtures, 'data', 'Si.cif')).get_structure()
            return instance

        if data_class is orm.BandsData:
            kpoints = orm.KpointsData()
            kpoints.set_cell([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
            kpoints.set_kpoints([[0., 0., 0.], [0.1, 0.1, 0.1]])
            instance = data_class()
            instance.set_kpointsdata(kpoints)
            instance.set_bands([[1.0, 2.0], [3.0, 4.0]])
            return instance

        if data_class is orm.TrajectoryData:
            instance = data_class()
            stepids = numpy.array([60])
            times = stepids * 0.01
            cells = numpy.array([[[3., 0., 0.], [0., 3., 0.], [0., 0., 3.]]])
            positions = numpy.array([[[0., 0., 0.]]])
            instance.set_trajectory(stepids=stepids,
                                    cells=cells,
                                    symbols=['H'],
                                    positions=positions,
                                    times=times)
            return instance

        if data_class is orm.UpfData:
            filepath_base = os.path.abspath(
                os.path.join(__file__, os.pardir, os.pardir, os.pardir,
                             'fixtures', 'pseudos'))
            filepath_carbon = os.path.join(filepath_base,
                                           'C_pbe_v1.2.uspp.F.UPF')
            instance = data_class(file=filepath_carbon)
            return instance

        raise RuntimeError(
            'no instance generator implemented for class `{}`. If you have added a `_prepare_*` method '
            'for this data class, add a generator of a dummy instance here'.
            format(data_class))
コード例 #11
0
def generate_cubic_grid(structure, centers, distance, dim):
    """Generate a cubic grids centered in `centers` of size `distance` and dimensionality `dim`.

    :param structure: aiida.orm.StructureData node  used to get the cell of the material.
    :param centers: aiida.orm.ArrayData containing an array named `centers`.
                    Each element of `centers` is used to generate a cubic grid around it.
    :param distance: aiida.orm.Float indicating the lateral size of the cubic grid.
    :param dim: aiida.orm.Int determining the dimensionality of the grid.
                e.g.: dim=1 -> 5x1x1   dim = 2 -> 5x5x1   dim = 3 -> 5x5x5

    :return: aiida.orm.KpointsData containing the generated grids.
    """
    if not isinstance(structure, orm.StructureData):
        raise InputValidationError(
            'Invalide type {} for parameter `structure`'.format(
                type(structure)))
    if not isinstance(centers, orm.ArrayData):
        raise InputValidationError(
            'Invalide type {} for parameter `centers`'.format(type(centers)))
    if not isinstance(distance, orm.Float):
        raise InputValidationError(
            'Invalide type {} for parameter `distance`'.format(type(distance)))

    npoints = 5

    centers = centers.get_array('pinned')
    dist = distance.value / (npoints - 1)
    dim = dim.value

    # yapf: disable
    l    = np.arange(-(npoints-1)//2, (npoints-1)//2 + 1) + ((npoints + 1)%2) * 0.5
    lx   = l
    ly   = l if dim > 1 else [0,]
    lz   = l if dim > 2 else [0,]
    grid = np.array(list(product(lx, ly, lz))) * dist

    res = np.empty((0,3))
    for n,c in enumerate(centers):
        new = c + grid
        if n == 0:
            attach = new
        else:
            old_tree = KDTree(res)
            new_tree = KDTree(new)

            query = new_tree.query_ball_tree(old_tree, r=dist*1.74)

            attach = np.array([new[n] for n,q in enumerate(query) if not q])

        if len(attach):
            res = np.vstack((res, attach))

    kpt = orm.KpointsData()
    kpt.set_cell_from_structure(structure)
    kpt.set_kpoints(res, cartesian=True)

    return kpt
コード例 #12
0
def generate_kpt_cross(structure, kpoints, step):
    """Generate a x,y,z cross around each point.

    :param structure: The StructureData to be used.
    :param kpoints: The original list of kpoints in crystal coordinates.
    :param step: The size of the step for the cross.

    :return: A KpointsData object containing all the kpt generated, including the original ones
    """
    if not isinstance(structure, orm.StructureData):
        raise InputValidationError(
            'Invalide type {} for parameter `structure`'.format(
                type(structure)))
    if not isinstance(kpoints, orm.ArrayData):
        raise InputValidationError(
            'Invalide type {} for parameter `kpoints`'.format(type(kpoints)))
    if not isinstance(step, orm.Float):
        raise InputValidationError(
            'Invalide type {} for parameter `step`'.format(type(step)))

    try:
        kpt_cryst = kpoints.get_array('kpoints')
    except:
        kpt_cryst = kpoints.get_array('crossings')
    try:
        skips = kpoints.get_array('skips')
    except:
        skips = [0] * len(kpt_cryst)

    step = step.value

    cell = structure.cell
    recipr = recipr_base(cell)
    kpts_cart = np.dot(kpt_cryst, recipr)

    # Apply cross shifts to original kpts
    shifts = np.array([
        [step, 0, 0],
        [0, step, 0],
        [0, 0, step],
        [0, 0, 0],
        [-step, 0, 0],
        [0, -step, 0],
        [0, 0, -step],
    ])
    app = np.empty((0, 3))
    for s, k in zip(skips, kpts_cart):
        if s:
            continue
        app = np.vstack((app, k + shifts))

    new_kpt = orm.KpointsData()
    new_kpt.set_cell(cell)
    new_kpt.set_kpoints(app, cartesian=True)

    return new_kpt
コード例 #13
0
def silicon_builder(db_test_app):
    """Prepare a mock - ready calculation for silicon"""
    silicon = orm.StructureData()
    r_unit = 2.6954645
    silicon.set_cell(np.array([[1, 1, 0], [1, 0, 1], [0, 1, 1]]) * r_unit)
    silicon.append_atom(symbols=["Si"], position=[0, 0, 0])
    silicon.append_atom(symbols=["Si"], position=[r_unit * 0.5] * 3)
    silicon.label = "Si"
    silicon.description = "A silicon structure"
    param_dict = {
        # Notice that the keywords are group into two sub-dictionaries
        # just like you would do when preparing the inputs by hand
        "CELL": {
            "symmetry_generate": True,
            "snap_to_symmetry": True,
            # Pass a list of string to set a BLOCK inputs
            #"cell_constraints":
            #["0 0 0", "0 0 0"]
        },
        "PARAM": {
            "task": "singlepoint",
            "basis_precision": "medium",
            "fix_occupancy":
            True,  # Use bool type to make it easy for querying
            "opt_strategy": "memory",
            "num_dump_cycles": 0,
            "write_formatted_density": True
        }
    }
    # We need to create a Dict node that holds the dictionary
    param = orm.Dict(dict=param_dict)
    kpoints = orm.KpointsData()
    # Use gamma and 0.25, 0.25, 0.25
    kpoints.set_kpoints_mesh((4, 4, 4), offset=(0, 0, 0))
    c9 = OTFGData(otfg_entry="C9")
    CastepCalculation = CalculationFactory('castep.castep')
    code_path = check_output(['which', 'castep.mock'],
                             universal_newlines=True).strip()
    castep_mock = orm.Code((db_test_app.localhost, code_path),
                           input_plugin_name='castep.castep')

    builder = CastepCalculation.get_builder()
    builder.structure = silicon
    builder.parameters = param
    builder.kpoints = kpoints
    builder.code = castep_mock
    builder.pseudos = {'Si': c9}
    builder.metadata.options.withmpi = False
    builder.metadata.options.resources = {
        'num_machines': 1,
        'tot_num_mpiprocs': 2
    }
    builder.metadata.options.max_wallclock_seconds = 600
    builder.metadata.label = "Si SINGLEPOINT"
    builder.metadata.description = 'A Example CASTEP calculation for silicon'
    return builder
コード例 #14
0
def crop_kpoints(structure, kpt_data, centers, radius):
    """Crop a given set of k-points `kpt_data` that are within a spherical radius `r` from a set of centers `centers`.

    :param structure: aiida.orm.StructureData used to get the cell of the material.
    :param kpt_data: aiida.orm.KpointsData to crop.
    :param centers: aiida.orm.ArrayData containing an array named `centers`.
                    Each element of `centers` is used as the center of a spherical cropping.
    :param radius: radius of the sphere cropping.

    :return: aiida.orm.KpointsData node containing the cropped kpoints
    """
    if not isinstance(structure, orm.StructureData):
        raise InputValidationError(
            'Invalide type {} for parameter `structure`'.format(
                type(structure)))
    if not isinstance(kpt_data, orm.KpointsData):
        raise InputValidationError(
            'Invalide type {} for parameter `kpt_data`'.format(type(kpt_data)))
    if not isinstance(centers, orm.ArrayData):
        raise InputValidationError(
            'Invalide type {} for parameter `centers`'.format(type(centers)))
    if not isinstance(radius, orm.Float):
        raise InputValidationError(
            'Invalide type {} for parameter `radius`'.format(type(radius)))
    centers = centers.get_array('centers')
    if len(centers.shape) != 2 or centers.shape[1] != 3:
        raise InputValidationError(
            'Invalide shape {} for array `centers`. Expected (*,3)'.format(
                centers.shape))

    r = radius.value
    cell = np.array(structure.cell)
    recipr = recipr_base(cell)

    try:
        kpt_cryst = np.array(kpt_data.get_kpoints_mesh(print_list=True))
    except MemoryError:
        return orm.Bool(False)
    kpt_cart = np.dot(kpt_cryst, recipr)

    c_cryst = centers
    c_cart = np.dot(c_cryst, recipr)

    kpt_cart = KDTree(kpt_cart)
    centers = KDTree(c_cart)

    query = kpt_cart.query_ball_tree(centers, r=r)

    where = [n for n, l in enumerate(query) if len(l)]

    new = orm.KpointsData()
    new.set_kpoints(kpt_cryst[where])

    return new
コード例 #15
0
def merge_kpoints_inline(mesh_kpoints, band_kpoints):
    """
    Merges the kpoints of mesh_kpoints and band_kpoints (in that order), giving weight 1 to the mesh_kpoints, and weight 0 to the band_kpoints.
    """
    band_kpoints_array = band_kpoints.get_kpoints()
    mesh_kpoints_array = mesh_kpoints.get_kpoints_mesh(print_list=True)
    weights = [1.] * len(mesh_kpoints_array) + [0.] * len(band_kpoints_array)
    kpoints = orm.KpointsData()
    kpoints.set_kpoints(
        np.vstack([mesh_kpoints_array, band_kpoints_array]), weights=weights
    )
    return {'kpoints': kpoints}
コード例 #16
0
    def parse(self, **kwargs):
        """Parse the retrieved files from a `MatdynCalculation`."""
        try:
            output_folder = self.retrieved
        except exceptions.NotExistent:
            return self.exit(self.exit_codes.ERROR_NO_RETRIEVED_FOLDER)

        filename_stdout = self.node.get_option('output_filename')
        filename_frequencies = MatdynCalculation._PHONON_FREQUENCIES_NAME

        if filename_stdout not in output_folder.list_object_names():
            return self.exit(self.exit_codes.ERROR_OUTPUT_STDOUT_READ)

        if 'JOB DONE' not in output_folder.get_object_content(filename_stdout):
            return self.exit(self.exit_codes.ERROR_OUTPUT_STDOUT_INCOMPLETE)

        if filename_frequencies not in output_folder.list_object_names():
            return self.exit(self.exit_codes.ERROR_OUTPUT_STDOUT_READ)

        # Extract the kpoints from the input data and create the `KpointsData` for the `BandsData`
        try:
            kpoints = self.node.inputs.kpoints.get_kpoints()
            kpoints_for_bands = self.node.inputs.kpoints.clone()
        except AttributeError:
            kpoints = self.node.inputs.kpoints.get_kpoints_mesh(
                print_list=True)
            kpoints_for_bands = orm.KpointsData()
            kpoints_for_bands.set_kpoints(kpoints)

        parsed_data = parse_raw_matdyn_phonon_file(
            output_folder.get_object_content(filename_frequencies))

        try:
            num_kpoints = parsed_data.pop('num_kpoints')
        except KeyError:
            return self.exit(self.exit_codes.ERROR_OUTPUT_KPOINTS_MISSING)

        if num_kpoints != kpoints.shape[0]:
            return self.exit(
                self.exit_codes.ERROR_OUTPUT_KPOINTS_INCOMMENSURATE)

        output_bands = orm.BandsData()
        output_bands.set_kpointsdata(kpoints_for_bands)
        output_bands.set_bands(parsed_data.pop('phonon_bands'), units='THz')

        for message in parsed_data['warnings']:
            self.logger.error(message)

        self.out('output_parameters', orm.Dict(dict=parsed_data))
        self.out('output_phonon_bands', output_bands)

        return
コード例 #17
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
コード例 #18
0
    def _generate_inputs(calculation_type='scf', settings=None, metadata=None):
        structure = generate_structure()
        parameters = {'CONTROL': {'calculation': calculation_type}}
        kpoints = orm.KpointsData()
        kpoints.set_cell_from_structure(structure)
        kpoints.set_kpoints_mesh_from_density(0.15)

        return AttributeDict({
            'structure': generate_structure(),
            'kpoints': kpoints,
            'parameters': orm.Dict(dict=parameters),
            'settings': orm.Dict(dict=settings),
            'metadata': metadata or {}
        })
コード例 #19
0
def test_bandspoints(aiida_profile, fixture_sandbox, generate_calc_job,
    fixture_code, generate_structure, generate_kpoints_mesh, generate_basis,
    generate_param, generate_psf_data, generate_psml_data, file_regression):
    """
    Test that the fdf file contains the correct information when a band structure
    calculation is required by the user and he/she specifies single k-points.
    """

    entry_point_name = 'siesta.siesta'

    psf = generate_psf_data('Si')
    psml = generate_psml_data('Si')

    structure=generate_structure()
    bandskpoints = orm.KpointsData()
    kpp = [(0.500,  0.250, 0.750), (0.500,  0.500, 0.500), (0., 0., 0.)]
    bandskpoints.set_cell(structure.cell, structure.pbc)
    bandskpoints.set_kpoints(kpp)


    inputs = {
        'bandskpoints': bandskpoints,
        'code': fixture_code(entry_point_name),
        'structure': structure,
        'kpoints': generate_kpoints_mesh(2),
        'parameters': generate_param(),
        'basis': generate_basis(),
        'pseudos': {
            'Si': psf,
            'SiDiff': psml
        },
        'metadata': {
            'options': {
               'resources': {'num_machines': 1  },
               'max_wallclock_seconds': 1800,
               'withmpi': False,
               }
        }
    }

    calc_info = generate_calc_job(fixture_sandbox, entry_point_name, inputs)

    retrieve_list = ['BASIS_ENTHALPY', 'MESSAGES','time.json','aiida.out','aiida.xml','aiida.bands','*.ion.xml']

    assert sorted(calc_info.retrieve_list) == sorted(retrieve_list)

    with fixture_sandbox.open('aiida.fdf') as handle:
        input_written = handle.read()

    file_regression.check(input_written, encoding='utf-8', extension='.fdf')
コード例 #20
0
    def inner():
        kpoints_mesh = orm.KpointsData()
        kpoints_mesh.set_kpoints_mesh([2, 2, 2])

        kpoints = orm.KpointsData()
        kpoints.set_kpoints([[x, x, x] for x in np.linspace(0, 0.5, 6)],
                            labels=((0, 'G'), (5, 'M')))

        return {
            'structure':
            insb_structure,
            'kpoints_mesh':
            kpoints_mesh,
            'kpoints':
            kpoints,
            'wannier_parameters':
            orm.Dict(dict=dict(num_wann=14,
                               num_bands=36,
                               spinors=True,
                               dis_num_iter=1000,
                               num_iter=0)),
            'wannier_projections':
            orm.List(list=['In : s; px; py; pz', 'Sb : px; py; pz']),
        }
コード例 #21
0
def test_siesta_bandspoints(aiida_profile, fixture_localhost, generate_calc_job_node,
    generate_parser, generate_structure, data_regression):
    """
    Test parsing of bands in a siesta calculation when the bandspoints option is set in the submission file.
    Also the time.json, MESSAGES and BASIS_ENTHALPY files are added, therefore their parsing is tested as well. The MESSAGES
    file is the standard containing only "INFO: Job completed". 
    """

    name = 'bandspoints'
    entry_point_calc_job = 'siesta.siesta'
    entry_point_parser = 'siesta.parser'

    structure=generate_structure()
    bandskpoints = orm.KpointsData()
    kpp = [(0.500,  0.250, 0.750), (0.500,  0.500, 0.500), (0., 0., 0.)]
    bandskpoints.set_cell(structure.cell, structure.pbc)
    bandskpoints.set_kpoints(kpp)

    inputs = AttributeDict({
        'structure': structure,
        'bandskpoints': bandskpoints
    })

    attributes=AttributeDict({'input_filename':'aiida.fdf', 'output_filename':'aiida.out', 'prefix':'aiida'})

    node = generate_calc_job_node(entry_point_calc_job, fixture_localhost, name, inputs, attributes)
    parser = generate_parser(entry_point_parser)
    results, calcfunction = parser.parse_from_node(node, store_provenance=False)

    assert calcfunction.is_finished
    assert calcfunction.exception is None
    assert calcfunction.is_finished_ok
    assert calcfunction.exit_message is None
    assert not orm.Log.objects.get_logs_for(node)
    assert 'forces_and_stress' in results
    assert 'output_parameters' in results
    assert 'output_structure' in results
    assert 'bands' in results 

    data_regression.check({
        'forces_and_stress': results['forces_and_stress'].attributes,
        'output_parameters': results['output_parameters'].get_dict(),
        'bands': results['bands'].attributes
    })
コード例 #22
0
def make_explicit_kpoints(kpoints_mesh):
    """
    Creates an explicit KpointsData from a KpointsData specified as
    a mesh.
    """
    mesh, offset = kpoints_mesh.get_kpoints_mesh()

    kpts_explicit_array = np.array(
        list(
            itertools.product(
                *[
                    np.linspace(0, 1, mesh_size, endpoint=False)
                    for mesh_size in mesh
                ]
            )
        )
    )
    kpts_explicit_array += offset
    kpts_explicit = orm.KpointsData()
    kpts_explicit.set_kpoints(kpts_explicit_array)
    return kpts_explicit
コード例 #23
0
ファイル: kpoint_grids.py プロジェクト: Crivella/mypyutils
def kpt_crop(kpoints: orm.KpointsData, centers: orm.ArrayData,
             radii: orm.ArrayData, anticrop: orm.Bool) -> orm.KpointsData:
    kpt_cryst = kpoints.get_kpoints_mesh(print_list=True)
    cell = kpoints.cell
    recipr = recipr_base(cell)

    centers = centers.get_array('centers')
    centers = centers.dot(recipr)
    radii = radii.get_array('radii')

    kpt, wgt = _kpt_crop(kpt_cryst,
                         recipr,
                         centers=centers,
                         radii=radii,
                         anticrop=anticrop.value)

    res = orm.KpointsData()
    res.set_cell(cell)
    res.set_kpoints(kpt, cartesian=True, weights=wgt)

    return res
コード例 #24
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
    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
コード例 #25
0
def generate_trim(structure: orm.StructureData,
                  dimensionality: orm.Int) -> orm.KpointsData:
    """Generate the TRIM point KpointsData for the given structure and dimensionality."""
    from itertools import product
    null = [0.0]
    l = [0.0, 0.5]

    dim = dimensionality.value

    if dim == 2:
        grid = np.array(list(product(l, l, null)))
    elif dim == 3:
        grid = np.array(list(product(l, l, l)))
    else:
        raise exceptions.InputValidationError(
            'Invalid dimensionality {}'.format(dim))

    res = orm.KpointsData()
    res.set_cell_from_structure(structure)
    res.set_kpoints(grid)

    return res
コード例 #26
0
def convert_kpoints_mesh_to_list(kmesh):
    """works just like kmesh.pl in Wannier90

    :param kmesh: [description]
    :type kmesh: KpointsData
    :raises ValueError: [description]
    :return: [description]
    :rtype: [type]
    """
    import numpy as np
    try:  # test if it is a mesh
        results = kmesh.get_kpoints_mesh()
    except AttributeError:
        try:
            kmesh.get_kpoints()
        except AttributeError as e:  # an empty input
            e.args = ('input kmesh is empty!', )
            raise e
        else:
            return kmesh
    else:
        # currently we ignore offset
        mesh = results[0]

        # following is similar to wannier90/kmesh.pl
        totpts = np.prod(mesh)
        weights = np.ones([totpts]) / totpts

        kpoints = np.zeros([totpts, 3])
        ind = 0
        for x in range(mesh[0]):
            for y in range(mesh[1]):
                for z in range(mesh[2]):
                    kpoints[ind, :] = [x / mesh[0], y / mesh[1], z / mesh[2]]
                    ind += 1
        klist = orm.KpointsData()
        klist.set_kpoints(kpoints=kpoints, cartesian=False, weights=weights)
        return klist
コード例 #27
0
def generate_inputs_relax():
    """Return only those inputs that the parser will expect to be there.

    This needs a separate input generation function from the default one, because the parser depends on certain values
    in the input parameters to determine what kind of calculation it was. For example, it will check the card
    `CONTROL.calculation` to determine whether the `TrajectoryData` should be attached. If we would not set it to
    `relax`, the parser would not parse that output node and the test would fail. Until we can make the raw output
    parser independent of the input parameters, this will have to remain a separate test inputs generator.
    """
    a = 5.43
    structure = orm.StructureData(
        cell=[[a / 2., a / 2., 0], [a / 2., 0, a / 2.], [0, a / 2., a / 2.]])
    structure.append_atom(position=(0., 0., 0.), symbols='Si', name='Si1')
    structure.append_atom(position=(a / 4., a / 4., a / 4.),
                          symbols='Si',
                          name='Si2')
    structure.store()

    parameters = {
        'CONTROL': {
            'calculation': 'relax'
        },
        'SYSTEM': {
            'ecutrho': 240.0,
            'ecutwfc': 30.0
        }
    }
    kpoints = orm.KpointsData()
    kpoints.set_cell_from_structure(structure)
    kpoints.set_kpoints_mesh_from_density(0.15)

    return AttributeDict({
        'structure': structure,
        'kpoints': kpoints,
        'parameters': orm.Dict(dict=parameters),
        'settings': orm.Dict()
    })
コード例 #28
0
def create_builder():
    builder = OptimizeFirstPrinciplesTightBinding.get_builder()

    # Add the input structure
    builder.structure = orm.StructureData()
    builder.structure.set_ase(read_vasp('inputs/POSCAR'))

    # Specify that QuantumEspressoFirstPrinciplesRun should be used to run the first-principles calculations
    builder.fp_run_workflow = QuantumEspressoFirstPrinciplesRun

    # Set the inputs for the QuantumEspressoFirstPrinciplesRun workflow
    common_qe_parameters = orm.Dict(
        dict=dict(
            CONTROL=dict(etot_conv_thr=1e-3),
            SYSTEM=dict(noncolin=True, lspinorb=True, nbnd=36, ecutwfc=30),
        )
    )

    pseudo_dir = pathlib.Path(__file__
                              ).parent.absolute() / 'inputs' / 'pseudos'
    pseudos = {
        'In':
        orm.UpfData(
            file=str(pseudo_dir / 'In.rel-pbe-dn-kjpaw_psl.1.0.0.UPF')
        ),
        'Sb':
        orm.UpfData(file=str(pseudo_dir / 'Sb.rel-pbe-n-kjpaw_psl.1.0.0.UPF'))
    }

    # We use the same general Quantum ESPRESSO parameters for the
    # scf, nscf, and bands calculations. The calculation type and
    # k-points will be set by the workflow.
    repeated_pw_inputs = {
        'pseudos': pseudos,
        'parameters': common_qe_parameters,
        'metadata': METADATA_PW,
        'code': CODE_PW
    }

    builder.fp_run = {
        'scf': repeated_pw_inputs,
        'bands': {
            'pw': repeated_pw_inputs
        },
        'to_wannier': {
            'nscf': repeated_pw_inputs,
            'pw2wannier': {
                'code': CODE_PW2WANNIER,
                'metadata': METADATA_PW2WANNIER
            },
            'wannier': {
                'code': CODE_WANNIER,
                'metadata': METADATA_WANNIER
            }
        }
    }

    # Setting the k-points for the reference bandstructure
    kpoints_list = []
    kvals = np.linspace(0, 0.5, 20, endpoint=False)
    kvals_rev = np.linspace(0.5, 0, 20, endpoint=False)
    for k in kvals_rev:
        kpoints_list.append((k, k, 0))  # Z to Gamma
    for k in kvals:
        kpoints_list.append((0, k, k))  # Gamma to X
    for k in kvals:
        kpoints_list.append((k, 0.5, 0.5))  # X to L
    for k in kvals_rev:
        kpoints_list.append((k, k, k))  # L to Gamma
    for k in np.linspace(0, 0.375, 21, endpoint=True):
        kpoints_list.append((k, k, 2 * k))  # Gamma to K
    builder.kpoints = orm.KpointsData()
    builder.kpoints.set_kpoints(
        kpoints_list,
        labels=[(i * 20, label)
                for i, label in enumerate(['Z', 'G', 'X', 'L', 'G', 'K'])]
    )

    # Setting the k-points mesh used to run the SCF and Wannier calculations
    builder.kpoints_mesh = orm.KpointsData()
    builder.kpoints_mesh.set_kpoints_mesh([6, 6, 6])

    builder.wannier.code = CODE_WANNIER
    builder.code_tbmodels = orm.Code.get_from_string('tbmodels')

    # Setting the workflow to evaluate the tight-binding models
    builder.model_evaluation_workflow = BandDifferenceModelEvaluation

    # Setting the additional inputs for the model evaluation workflow
    builder.model_evaluation = dict(
        code_bands_inspect=orm.Code.get_from_string('bands_inspect')
    )

    # Set the initial energy window value
    builder.initial_window = orm.List(list=[-1, 3, 10, 18])

    # Tolerance for the energy window.
    builder.window_tol = orm.Float(1.5)
    # Tolerance for the 'cost_value'.
    builder.cost_tol = orm.Float(0.3)
    # The tolerances are set higher than might be appropriate for a 'production'
    # run to make the example run more quickly.

    # Setting the parameters for Wannier90
    builder.wannier_parameters = orm.Dict(
        dict=dict(
            num_wann=14,
            num_bands=36,
            dis_num_iter=100,
            num_iter=0,
            spinors=True,
            # exclude_bands=range(1, )
        )
    )
    # Choose the Wannier90 trial orbitals
    builder.wannier_projections = orm.List(
        list=['In : s; pz; px; py', 'Sb : pz; px; py']
    )
    # Set the resource requirements for the Wannier90 run
    builder.wannier.metadata = METADATA_WANNIER

    # Set the symmetry file
    builder.symmetries = orm.SinglefileData(
        file=os.path.abspath('inputs/symmetries.hdf5')
    )

    # Pick the relevant bands from the reference calculation
    builder.slice_reference_bands = orm.List(list=list(range(12, 26)))

    return builder
コード例 #29
0
    def get_builder(self,
                    structure: StructureData,
                    calc_engines: Dict[str, Any],
                    *,
                    protocol: str = None,
                    relax_type: RelaxType = RelaxType.ATOMS,
                    electronic_type: ElectronicType = ElectronicType.METAL,
                    spin_type: SpinType = SpinType.NONE,
                    magnetization_per_site: List[float] = None,
                    threshold_forces: float = None,
                    threshold_stress: float = None,
                    previous_workchain=None,
                    **kwargs) -> engine.ProcessBuilder:
        """Return a process builder for the corresponding workchain class with inputs set according to the protocol.

        :param structure: the structure to be relaxed.
        :param calc_engines: a dictionary containing the computational resources for the relaxation.
        :param protocol: the protocol to use when determining the workchain inputs.
        :param relax_type: the type of relaxation to perform.
        :param electronic_type: the electronic character that is to be used for the structure.
        :param spin_type: the spin polarization type to use for the calculation.
        :param magnetization_per_site: a list with the initial spin polarization for each site. Float or integer in
            units of electrons. If not defined, the builder will automatically define the initial magnetization if and
            only if `spin_type != SpinType.NONE`.
        :param threshold_forces: target threshold for the forces in eV/Å.
        :param threshold_stress: target threshold for the stress in eV/Å^3.
        :param previous_workchain: a <Code>RelaxWorkChain node.
        :param kwargs: any inputs that are specific to the plugin.
        :return: a `aiida.engine.processes.ProcessBuilder` instance ready to be submitted.
        """
        # pylint: disable=too-many-locals,too-many-branches,too-many-statements
        protocol = protocol or self.get_default_protocol_name()

        super().get_builder(structure,
                            calc_engines,
                            protocol=protocol,
                            relax_type=relax_type,
                            electronic_type=electronic_type,
                            spin_type=spin_type,
                            magnetization_per_site=magnetization_per_site,
                            threshold_forces=threshold_forces,
                            threshold_stress=threshold_stress,
                            previous_workchain=previous_workchain,
                            **kwargs)

        protocol = copy.deepcopy(self.get_protocol(protocol))
        code = calc_engines['relax']['code']
        pseudo_family = orm.Group.objects.get(
            label=protocol.pop('pseudo_family'))
        override = {
            'abinit': {
                'metadata': {
                    'options': calc_engines['relax']['options']
                },
                'pseudos': pseudo_family.get_pseudos(structure=structure)
            }
        }

        builder = self.process_class.get_builder()
        inputs = generate_inputs(self.process_class._process_class, protocol,
                                 code, structure, override)  # pylint: disable=protected-access
        builder._update(inputs)  # pylint: disable=protected-access

        # RelaxType
        if relax_type == RelaxType.NONE:
            builder.abinit['parameters'][
                'ionmov'] = 0  # do not move the ions, Abinit default
        elif relax_type == RelaxType.ATOMS:
            # protocol defaults to ATOMS
            pass
        elif relax_type == RelaxType.ATOMS_CELL:
            builder.abinit['parameters'][
                'optcell'] = 2  # fully optimize the cell geometry
            builder.abinit['parameters'][
                'dilatmx'] = 1.15  # book additional mem. for p.w. basis exp.
        elif relax_type == RelaxType.ATOMS_VOLUME:
            builder.abinit['parameters']['optcell'] = 1  # optimize volume only
            builder.abinit['parameters'][
                'dilatmx'] = 1.15  # book additional mem. for p.w. basis exp.
        elif relax_type == RelaxType.ATOMS_SHAPE:
            builder.abinit['parameters'][
                'optcell'] = 3  # constant-volume optimization of cell geometry
        else:
            raise ValueError('relax type `{}` is not supported'.format(
                relax_type.value))

        # SpinType
        if spin_type == SpinType.NONE:
            # protocol defaults to NONE
            pass
        elif spin_type == SpinType.COLLINEAR:
            if np.all(np.isclose(magnetization_per_site, 0)):
                warnings.warn(
                    'All initial magnetizations are 0, doing a non-spin-polarized calculation.'
                )
            elif np.isclose(sum(magnetization_per_site),
                            0):  # antiferromagnetic
                builder.abinit['parameters'][
                    'nsppol'] = 1  # antiferromagnetic system
                builder.abinit['parameters'][
                    'nspden'] = 2  # scalar spin-magnetization in the z-axis
                builder.abinit['parameters']['spinat'] = [
                    [0.0, 0.0, mag] for mag in magnetization_per_site
                ]
            else:  # ferromagnetic
                builder.abinit['parameters'][
                    'nsppol'] = 2  # collinear spin-polarization
                builder.abinit['parameters'][
                    'nspden'] = 2  # scalar spin-magnetization in the z-axis
                builder.abinit['parameters']['spinat'] = [
                    [0.0, 0.0, mag] for mag in magnetization_per_site
                ]
        elif spin_type == SpinType.NON_COLLINEAR:
            # LATER: support vector magnetization_per_site
            builder.abinit['parameters']['nspinor'] = 2  # w.f. as spinors
            builder.abinit['parameters'][
                'nsppol'] = 1  # spin-up and spin-down can't be disentangled
            builder.abinit['parameters']['nspden'] = 4  # vector magnetization
            builder.abinit['parameters']['spinat'] = [
                [0.0, 0.0, mag] for mag in magnetization_per_site
            ]
        elif spin_type == SpinType.SPIN_ORBIT:
            if 'fr' not in protocol['pseudo_family']:
                raise ValueError('You must use the `stringent` protocol for SPIN_ORBIT calculations because '\
                    'it provides fully-relativistic pseudopotentials (`fr` is not in the protocol\'s '\
                    '`pseudo_family` entry).')
            builder.abinit['parameters']['nspinor'] = 2  # w.f. as spinors
        else:
            raise ValueError('spin type `{}` is not supported'.format(
                spin_type.value))

        # ElectronicType
        if electronic_type == ElectronicType.METAL:
            # protocal defaults to METAL
            pass
        elif electronic_type == ElectronicType.INSULATOR:
            # LATER: Support magnetization with insulators
            if spin_type not in [SpinType.NONE, SpinType.SPIN_ORBIT]:
                raise ValueError(
                    '`spin_type` {} is not supported for insulating systems.'.
                    format(spin_type.value))
            builder.abinit['parameters'][
                'occopt'] = 1  # fixed occupations, Abinit default
            builder.abinit['parameters']['fband'] = 0.125  # Abinit default
        else:
            raise ValueError('electronic type `{}` is not supported'.format(
                electronic_type.value))

        # force and stress thresholds
        if threshold_forces is not None:
            # The Abinit threshold_forces is in Ha/Bohr
            threshold_f = threshold_forces * units.eV_to_Ha / units.ang_to_bohr  # eV/Å
        else:
            # ABINIT default value. Set it explicitly in case it is changed.
            # How can we warn the user that we are using the tolxmf Abinit default value?
            threshold_f = 5.0e-5
        builder.abinit['parameters']['tolmxf'] = threshold_f
        if threshold_stress is not None:
            threshold_s = threshold_stress * units.eV_to_Ha / units.ang_to_bohr**3  # eV/Å^3
            strfact = threshold_f / threshold_s
            builder.abinit['parameters']['strfact'] = strfact

        # previous workchain
        if previous_workchain is not None:
            try:
                previous_kpoints = previous_workchain.inputs.kpoints
            except exceptions.NotExistentAttributeError:
                query_builder = orm.QueryBuilder()
                query_builder.append(orm.WorkChainNode,
                                     tag='relax',
                                     filters={'id': previous_workchain.id})
                query_builder.append(
                    orm.WorkChainNode,
                    tag='base',
                    with_incoming='relax',
                )
                query_builder.append(
                    orm.CalcFunctionNode,
                    tag='calcfunc',
                    edge_filters={'label': 'create_kpoints_from_distance'},
                    with_incoming='base')
                query_builder.append(orm.KpointsData,
                                     tag='kpoints',
                                     with_incoming='calcfunc')
                query_builder.order_by({orm.KpointsData: {'ctime': 'desc'}})
                query_builder_result = query_builder.all()
                if query_builder_result == []:
                    raise ValueError(
                        f'Could not find KpointsData associated with {previous_workchain}'
                    )
                previous_kpoints = query_builder_result[0][0]

            # ensure same k-points
            previous_kpoints_mesh, previous_kpoints_offset = previous_kpoints.get_kpoints_mesh(
            )
            new_kpoints = orm.KpointsData()
            new_kpoints.set_cell_from_structure(structure)
            new_kpoints.set_kpoints_mesh(previous_kpoints_mesh,
                                         previous_kpoints_offset)
            builder.kpoints = new_kpoints

            # ensure same k-points shift
            shiftk = previous_workchain.inputs.abinit__parameters.get_dict(
            ).get('shiftk', None)
            if shiftk is not None:
                builder.abinit['parameters']['shiftk'] = shiftk

            nshiftk = previous_workchain.inputs.abinit__parameters.get_dict(
            ).get('nshiftk', None)
            if nshiftk is not None:
                builder.abinit['parameters']['nshiftk'] = nshiftk

        return builder
コード例 #30
0
    def get_builder(self,
                    structure: StructureData,
                    engines: Dict[str, Any],
                    *,
                    protocol: str = None,
                    relax_type: RelaxType = RelaxType.POSITIONS,
                    electronic_type: ElectronicType = ElectronicType.METAL,
                    spin_type: SpinType = SpinType.NONE,
                    magnetization_per_site: List[float] = None,
                    threshold_forces: float = None,
                    threshold_stress: float = None,
                    reference_workchain=None,
                    **kwargs) -> engine.ProcessBuilder:
        """Return a process builder for the corresponding workchain class with inputs set according to the protocol.

        :param structure: the structure to be relaxed.
        :param engines: a dictionary containing the computational resources for the relaxation.
        :param protocol: the protocol to use when determining the workchain inputs.
        :param relax_type: the type of relaxation to perform.
        :param electronic_type: the electronic character that is to be used for the structure.
        :param spin_type: the spin polarization type to use for the calculation.
        :param magnetization_per_site: a list with the initial spin polarization for each site. Float or integer in
            units of electrons. If not defined, the builder will automatically define the initial magnetization if and
            only if `spin_type != SpinType.NONE`.
        :param threshold_forces: target threshold for the forces in eV/Å.
        :param threshold_stress: target threshold for the stress in eV/Å^3.
        :param reference_workchain: a <Code>CommonRelaxWorkChain node.
        :param kwargs: any inputs that are specific to the plugin.
        :return: a `aiida.engine.processes.ProcessBuilder` instance ready to be submitted.
        """
        # pylint: disable=too-many-locals,too-many-branches,too-many-statements
        protocol = protocol or self.get_default_protocol_name()

        super().get_builder(structure,
                            engines,
                            protocol=protocol,
                            relax_type=relax_type,
                            electronic_type=electronic_type,
                            spin_type=spin_type,
                            magnetization_per_site=magnetization_per_site,
                            threshold_forces=threshold_forces,
                            threshold_stress=threshold_stress,
                            reference_workchain=reference_workchain,
                            **kwargs)

        protocol = copy.deepcopy(self.get_protocol(protocol))
        code = engines['relax']['code']

        pseudo_family = orm.Group.objects.get(
            label=protocol.pop('pseudo_family'))
        cutoff_stringency = protocol['cutoff_stringency']
        pseudo_type = pseudo_family.pseudo_type
        # Recommended cutoffs from `aiida-pseudo` are in eV
        recommended_ecut_wfc, recommended_ecut_rho = pseudo_family.get_recommended_cutoffs(
            structure=structure, stringency=cutoff_stringency)
        if pseudo_type == 'pseudo.jthxml':
            # JTH XML are PAW; we need `pawecutdg`
            cutoff_parameters = {
                'ecut': np.ceil(recommended_ecut_wfc / units.Ha_to_eV),
                'pawecutdg': np.ceil(recommended_ecut_rho / units.Ha_to_eV),
            }
        else:
            # All others are NC; no need for `pawecutdg`
            cutoff_parameters = {'ecut': recommended_ecut_wfc / units.Ha_to_eV}

        override = {
            'abinit': {
                'metadata': {
                    'options': engines['relax']['options']
                },
                'pseudos': pseudo_family.get_pseudos(structure=structure),
                'parameters': cutoff_parameters
            }
        }

        builder = self.process_class.get_builder()

        # Force threshold
        # NB we deal with this here because knowing threshold_f is necessary if the structure is a molecule.
        #   threshold_f will be used later in the generator to set the relax threshold
        #   (find "Continue force and stress thresholds" in this file.)
        if threshold_forces is not None:
            threshold_f = threshold_forces * units.eV_to_Ha / units.ang_to_bohr  # eV/Å -> Ha/Bohr
        else:
            threshold_f = 5.0e-5  # Abinit default value in Ha/Bohr

        # Deal with molecular case
        if structure.pbc == (False, False, False):
            # We assume the structure is a molecule which already has an appropriate vacuum applied
            # NB: the vacuum around the molecule must maintain the molecule's symmetries!
            warnings.warn(
                f'The input structure {structure} has no periodic boundary conditions, so we '
                'assume the structure is a molecule. The structure will be modified to have full PBC. We assume that '
                'the cell contains appropriate symmetry-conserving vacuum, and various tweaks for molecular systems '
                ' will be applied to the selected protocol!')

            # Set pbc to [True, True, True]
            pbc_structure = structure.clone()
            pbc_structure.set_pbc([True, True, True])

            # Update protocol
            _ = protocol['base'].pop(
                'kpoints_distance'
            )  # Remove k-points distance; we will use gamma only
            _ = protocol['base']['abinit']['parameters'].pop(
                'tolvrs')  # Remove tolvrs; we will use force tolerance for SCF
            # Set k-points to gamma-point
            protocol['base']['kpoints'] = [1, 1, 1]
            protocol['base']['abinit']['parameters']['shiftk'] = [[0, 0, 0]]
            protocol['base']['abinit']['parameters']['nkpt'] = 1
            # Set a force tolerance for SCF convergence
            protocol['base']['abinit']['parameters'][
                'toldff'] = threshold_f * 1.0e-1
            # Add a model macroscopic dielectric constant
            protocol['base']['abinit']['parameters']['diemac'] = 2.0

            inputs = generate_inputs(self.process_class._process_class,
                                     protocol, code, pbc_structure, override)  # pylint: disable=protected-access
        elif False in structure.pbc:
            raise ValueError(
                f'The input structure has periodic boundary conditions {structure.pbc}, but partial '
                'periodic boundary conditions are not supported.')
        else:
            inputs = generate_inputs(self.process_class._process_class,
                                     protocol, code, structure, override)  # pylint: disable=protected-access

        builder._update(inputs)  # pylint: disable=protected-access

        # RelaxType
        if relax_type == RelaxType.NONE:
            builder.abinit['parameters'][
                'ionmov'] = 0  # do not move the ions, Abinit default
        elif relax_type == RelaxType.POSITIONS:
            # protocol defaults to POSITIONS
            pass
        elif relax_type == RelaxType.POSITIONS_CELL:
            builder.abinit['parameters'][
                'optcell'] = 2  # fully optimize the cell geometry
            builder.abinit['parameters'][
                'dilatmx'] = 1.15  # book additional mem. for p.w. basis exp.
        elif relax_type == RelaxType.POSITIONS_VOLUME:
            builder.abinit['parameters']['optcell'] = 1  # optimize volume only
            builder.abinit['parameters'][
                'dilatmx'] = 1.15  # book additional mem. for p.w. basis exp.
        elif relax_type == RelaxType.POSITIONS_SHAPE:
            builder.abinit['parameters'][
                'optcell'] = 3  # constant-volume optimization of cell geometry
            builder.abinit['parameters'][
                'dilatmx'] = 1.05  # book additional mem. for p.w. basis exp.
        else:
            raise ValueError('relax type `{}` is not supported'.format(
                relax_type.value))

        # SpinType
        if spin_type == SpinType.NONE:
            # protocol defaults to NONE
            pass
        elif spin_type == SpinType.COLLINEAR:
            if magnetization_per_site is None:
                magnetization_per_site = get_initial_magnetization(structure)
                warnings.warn(
                    f'input magnetization per site was None, setting it to {magnetization_per_site}'
                )
            magnetization_per_site = np.array(magnetization_per_site)

            sum_is_zero = np.isclose(sum(magnetization_per_site), 0.0)
            all_are_zero = np.all(np.isclose(magnetization_per_site, 0.0))
            non_zero_mags = magnetization_per_site[
                ~np.isclose(magnetization_per_site, 0.0)]
            all_non_zero_pos = np.all(non_zero_mags > 0.0)
            all_non_zero_neg = np.all(non_zero_mags < 0.0)

            if all_are_zero:  # non-magnetic
                warnings.warn(
                    'all of the initial magnetizations per site are close to zero; doing a non-spin-polarized '
                    'calculation')
            elif ((sum_is_zero and not all_are_zero)
                  or (not all_non_zero_pos
                      and not all_non_zero_neg)):  # antiferromagnetic
                print('Detected antiferromagnetic!')
                builder.abinit['parameters'][
                    'nsppol'] = 1  # antiferromagnetic system
                builder.abinit['parameters'][
                    'nspden'] = 2  # scalar spin-magnetization in the z-axis
                builder.abinit['parameters']['spinat'] = [
                    [0.0, 0.0, mag] for mag in magnetization_per_site
                ]
            elif not all_are_zero and (all_non_zero_pos
                                       or all_non_zero_neg):  # ferromagnetic
                print('Detected ferromagnetic!')
                builder.abinit['parameters'][
                    'nsppol'] = 2  # collinear spin-polarization
                builder.abinit['parameters'][
                    'nspden'] = 2  # scalar spin-magnetization in the z-axis
                builder.abinit['parameters']['spinat'] = [
                    [0.0, 0.0, mag] for mag in magnetization_per_site
                ]
            else:
                raise ValueError(
                    f'Initial magnetization {magnetization_per_site} is ambiguous'
                )
        elif spin_type == SpinType.NON_COLLINEAR:
            # LATER: support vector magnetization_per_site
            builder.abinit['parameters']['nspinor'] = 2  # w.f. as spinors
            builder.abinit['parameters'][
                'nsppol'] = 1  # spin-up and spin-down can't be disentangled
            builder.abinit['parameters']['nspden'] = 4  # vector magnetization
            builder.abinit['parameters']['spinat'] = [
                [0.0, 0.0, mag] for mag in magnetization_per_site
            ]
        elif spin_type == SpinType.SPIN_ORBIT:
            builder.abinit['parameters']['nspinor'] = 2  # w.f. as spinors
            builder.abinit['parameters'][
                'kptopt'] = 4  # no time-reversal symmetry
        else:
            raise ValueError('spin type `{}` is not supported'.format(
                spin_type.value))

        # ElectronicType
        if electronic_type == ElectronicType.METAL:
            # protocal defaults to METAL
            pass
        elif electronic_type == ElectronicType.INSULATOR:
            # LATER: Support magnetization with insulators
            if spin_type not in [SpinType.NONE, SpinType.SPIN_ORBIT]:
                raise ValueError(
                    '`spin_type` {} is not supported for insulating systems.'.
                    format(spin_type.value))
            builder.abinit['parameters'][
                'occopt'] = 1  # fixed occupations, Abinit default
            builder.abinit['parameters']['fband'] = 0.125  # Abinit default
        else:
            raise ValueError('electronic type `{}` is not supported'.format(
                electronic_type.value))

        # Continue force and stress thresholds from above (see molecule treatment)
        builder.abinit['parameters']['tolmxf'] = threshold_f
        if threshold_stress is not None:
            threshold_s = threshold_stress * units.eV_to_Ha / units.ang_to_bohr**3  # eV/Å^3
            strfact = threshold_f / threshold_s
            builder.abinit['parameters']['strfact'] = strfact

        # previous workchain
        if reference_workchain is not None:
            try:
                previous_kpoints = reference_workchain.inputs.kpoints
            except exceptions.NotExistentAttributeError as not_existent_attr_error:
                query_builder = orm.QueryBuilder()
                query_builder.append(orm.WorkChainNode,
                                     tag='relax',
                                     filters={'id': reference_workchain.id})
                query_builder.append(
                    orm.WorkChainNode,
                    tag='base',
                    with_incoming='relax',
                )
                query_builder.append(
                    orm.CalcFunctionNode,
                    tag='calcfunc',
                    edge_filters={'label': 'create_kpoints_from_distance'},
                    with_incoming='base')
                query_builder.append(orm.KpointsData,
                                     tag='kpoints',
                                     with_incoming='calcfunc')
                query_builder.order_by({orm.KpointsData: {'ctime': 'desc'}})
                query_builder_result = query_builder.all()
                if query_builder_result == []:
                    msg = f'Could not find KpointsData associated with {reference_workchain}'
                    raise ValueError(msg) from not_existent_attr_error
                previous_kpoints = query_builder_result[0][0]

            # ensure same k-points
            previous_kpoints_mesh, previous_kpoints_offset = previous_kpoints.get_kpoints_mesh(
            )
            new_kpoints = orm.KpointsData()
            new_kpoints.set_cell_from_structure(structure)
            new_kpoints.set_kpoints_mesh(previous_kpoints_mesh,
                                         previous_kpoints_offset)
            builder.kpoints = new_kpoints

            # ensure same k-points shift
            shiftk = reference_workchain.inputs.abinit__parameters.get_dict(
            ).get('shiftk', None)
            if shiftk is not None:
                builder.abinit['parameters']['shiftk'] = shiftk

            nshiftk = reference_workchain.inputs.abinit__parameters.get_dict(
            ).get('nshiftk', None)
            if nshiftk is not None:
                builder.abinit['parameters']['nshiftk'] = nshiftk

        return builder