Skip to content

Commit

Permalink
Add the verification-pbe-v1 protocol for BigDFT (#310)
Browse files Browse the repository at this point in the history
  • Loading branch information
ljbeal authored Jul 12, 2023
1 parent 7d7cc9f commit 34ef55f
Showing 1 changed file with 76 additions and 103 deletions.
179 changes: 76 additions & 103 deletions aiida_common_workflows/workflows/relax/bigdft/generator.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
"""Implementation of `aiida_common_workflows.common.relax.generator.CommonRelaxInputGenerator` for BigDFT."""
from aiida import engine, orm, plugins
from aiida.engine import calcfunction

from aiida_common_workflows.common import ElectronicType, RelaxType, SpinType
from aiida_common_workflows.generators import ChoiceType, CodeType
Expand All @@ -14,77 +13,41 @@
StructureData = plugins.DataFactory('structure')


@calcfunction
def ortho_struct(input_struct):
"""Create and update a dict to pass to transform_to_orthorombic,
and then get back data to the input dict """
dico = dict()
dico['name'] = input_struct.sites[0].kind_name
dico['a'] = round(input_struct.cell_lengths[0], 6)
dico['alpha'] = round(input_struct.cell_angles[0], 6)
dico['b'] = round(input_struct.cell_lengths[1], 6)
dico['beta'] = round(input_struct.cell_angles[1], 6)
dico['c'] = round(input_struct.cell_lengths[2], 6)
dico['gamma'] = round(input_struct.cell_angles[2], 6)
dico['nat'] = len(input_struct.sites)
# use abc coordinates
import pymatgen
periodic = 0
for i in range(dico['nat']):
site = input_struct.get_pymatgen().sites[i]
if isinstance(site, pymatgen.core.sites.PeriodicSite):
periodic = 1
dico[str(i + 1)] = list(site.frac_coords)
else:
dico[str(i + 1)] = (site.coords[0] / dico['a'], site.coords[1] / dico['b'], site.coords[2] / dico['c'])
BigDFTParameters.transform_to_orthorombic(dico)
output = input_struct.clone()
output.clear_sites()
output.cell = [[dico['a'], 0, 0], [0, dico['b'], 0], [0, 0, dico['c']]]
for i in range(dico['nat']):
site = input_struct.sites[0]
if periodic == 1:
site.position = (
dico[str(i + 1)][0] * dico['a'], dico[str(i + 1)][1] * dico['b'], dico[str(i + 1)][2] * dico['c']
)
else:
site.position = dico[str(i + 1)]
output.append_site(site)
out = {'outstruct': output, 'outdict': dico}
return out


class BigDftCommonRelaxInputGenerator(CommonRelaxInputGenerator):
"""Input generator for the `BigDftCommonRelaxWorkChain`."""

_default_protocol = 'moderate'
_protocols = {
'fast': {
'description': 'This profile should be chosen if speed is more important than accuracy.',
'description': 'This profile should be chosen if accurate forces are required, but there is no need for '
'extremely accurate energies.',
'inputdict_cubic': {
'logfile': 'Yes',
'dft': {
'ixc': 'PBE',
'ncong': 2,
'rmult': [10, 8],
'itermax': 3,
'idsx': 0,
'gnrm_cv': 1e-7,
'hgrids': 0.45
'gnrm_cv': 1e-8,
'hgrids': 0.4,
'disablesym': 'no'
},
'mix': {
'iscf': 7,
'iscf': 17,
'itrpmax': 200,
'rpnrm_cv': 1e-10,
'tel': 1e-3,
'alphamix': 0.5,
'norbsempty': 1000,
'rpnrm_cv': 1.E-12,
'norbsempty': 120,
'tel': 0.00225,
'occopt': 2,
'alphamix': 0.8,
'alphadiis': 1.0
}
},
'inputdic_linear': {
'import': 'linear_fast'
'inputdict_linear': {
'import': 'linear'
},
'kpoints_distance': 20
'kpoints_distance': 142 # Equivalent length of K-space resolution (Bohr)
},
'moderate': {
'description': 'This profile should be chosen if accurate forces are required, but there is no need for '
Expand All @@ -98,49 +61,86 @@ class BigDftCommonRelaxInputGenerator(CommonRelaxInputGenerator):
'itermax': 3,
'idsx': 0,
'gnrm_cv': 1e-8,
'hgrids': 0.3
'hgrids': 0.4,
'disablesym': 'no'
},
'mix': {
'iscf': 7,
'iscf': 17,
'itrpmax': 200,
'rpnrm_cv': 1e-12,
'tel': 1e-3,
'alphamix': 0.5,
'norbsempty': 1000,
'rpnrm_cv': 1.E-12,
'norbsempty': 120,
'tel': 0.00225,
'occopt': 2,
'alphamix': 0.8,
'alphadiis': 1.0
}
},
'inputdict_linear': {
'import': 'linear'
},
'kpoints_distance': 40
'kpoints_distance': 274
},
'precise': {
'description': 'This profile should be chosen if highly accurate energy differences are required.',
'description': 'This profile should be chosen if accurate forces are required, but there is no need for '
'extremely accurate energies.',
'inputdict_cubic': {
'logfile': 'Yes',
'dft': {
'ixc': 'PBE',
'ncong': 2,
'rmult': [10, 8],
'itermax': 3,
'idsx': 0,
'gnrm_cv': 1e-8,
'hgrids': 0.3,
'disablesym': 'no'
},
'mix': {
'iscf': 17,
'itrpmax': 200,
'rpnrm_cv': 1.E-12,
'norbsempty': 120,
'tel': 0.00225,
'occopt': 2,
'alphamix': 0.8,
'alphadiis': 1.0
}
},
'inputdict_linear': {
'import': 'linear'
},
'kpoints_distance': 274
},
'verification-pbe-v1': {
'description': 'Protocol used for bulk run of EoS verification project',
'inputdict_cubic': {
'logfile': 'Yes',
'dft': {
'ixc': 'PBE',
'ncong': 2,
'rmult': [10, 8],
'itermax': 3,
'idsx': 0,
'gnrm_cv': 1e-8,
'hgrids': 0.15
'hgrids': 0.4,
'disablesym': 'no'
},
'mix': {
'iscf': 7,
'iscf': 17,
'itrpmax': 200,
'rpnrm_cv': 1e-12,
'tel': 1e-3,
'alphamix': 0.5,
'norbsempty': 1000,
'rpnrm_cv': 1.E-12,
'norbsempty': 120,
'tel': 0.00225,
'occopt': 2,
'alphamix': 0.8,
'alphadiis': 1.0
}
},
'inputdict_linear': {
'import': 'linear_accurate'
'import': 'linear'
},
}
'kpoints_distance': 274
},
}

@classmethod
Expand All @@ -150,6 +150,7 @@ def define(cls, spec):
The ports defined on the specification are the inputs that will be accepted by the ``get_builder`` method.
"""
super().define(spec)
spec.inputs['protocol'].valid_type = ChoiceType(('fast', 'moderate', 'precise', 'verification-PBE-v1'))
spec.inputs['spin_type'].valid_type = ChoiceType((SpinType.NONE, SpinType.COLLINEAR))
spec.inputs['relax_type'].valid_type = ChoiceType((RelaxType.NONE, RelaxType.POSITIONS))
spec.inputs['electronic_type'].valid_type = ChoiceType((ElectronicType.METAL, ElectronicType.INSULATOR))
Expand All @@ -161,6 +162,8 @@ def _construct_builder(self, **kwargs) -> engine.ProcessBuilder:
The keyword arguments will have been validated against the input generator specification.
"""
# pylint: disable=too-many-branches,too-many-statements,too-many-locals
import copy

structure = kwargs['structure']
engines = kwargs['engines']
protocol = kwargs['protocol']
Expand All @@ -181,20 +184,9 @@ def _construct_builder(self, **kwargs) -> engine.ProcessBuilder:
else:
raise ValueError(f'relaxation type `{relax_type.value}` is not supported')

pymatgen_struct = structure.get_pymatgen()
ortho_dict = None
if pymatgen_struct.ntypesp <= 1:
# pass the structure through a transform to generate orthorhombic structure if possible/needed.
new = ortho_struct(structure)
newstruct = new.get('outstruct')
ortho_dict = new.get('outdict')
newstruct.store()
builder.structure = newstruct
else:
builder.structure = structure
builder.structure = structure

# for now apply simple stupid heuristic : atoms < 200 -> cubic, else -> linear.
import copy
if len(builder.structure.sites) <= 200:
inputdict = copy.deepcopy(self.get_protocol(protocol)['inputdict_cubic'])
else:
Expand All @@ -207,11 +199,10 @@ def _construct_builder(self, **kwargs) -> engine.ProcessBuilder:
hgrids = logfile[0].get('dft').get('hgrids')
else:
hgrids = logfile.get('dft').get('hgrids')
inputdict['dft']['hgrids'] = hgrids[0] * builder.structure.cell_lengths[0] / \
first_hgrid = hgrids[0] if isinstance(hgrids, list) else hgrids
inputdict['dft']['hgrids'] = first_hgrid * builder.structure.cell_lengths[0] / \
reference_workchain.inputs.structure.cell_lengths[0]


# Soon : Use inputActions
if electronic_type is ElectronicType.METAL:
if 'mix' not in inputdict:
inputdict['mix'] = {}
Expand All @@ -228,32 +219,14 @@ def _construct_builder(self, **kwargs) -> engine.ProcessBuilder:
inputdict['dft'].update({'nspin': 1})
elif spin_type is SpinType.COLLINEAR:
inputdict['dft'].update({'nspin': 2})
psp = []
if ortho_dict is not None:
inputdict = BigDFTParameters.set_inputfile(
inputdict['dft']['hgrids'], ortho_dict, inputdict, psp=psp, units='angstroem'
)
else:
# use HGH pseudopotentials instead of default ones from BigDFT, if the user does not specify new ones.
# This may be moved to the plugin if we decide to make it the default behavior.
for elem in pymatgen_struct.types_of_specie:
BigDFTParameters.set_psp(elem.name, psp)
inputdict['kpt'] = BigDFTParameters.set_kpoints(len(builder.structure.sites))
if pymatgen_struct.ntypesp <= 1:
inputdict['dft'].update(
BigDFTParameters.set_spin(builder.structure.sites[0].kind_name, len(builder.structure.sites))
)

if magnetization_per_site:
for (i, atom) in enumerate(inputdict['posinp']['positions']):
atom['IGSpin'] = int(magnetization_per_site[i])
# correctly set kpoints from protocol fast and moderate. If precise, use the ones from set_inputfile/set_kpt
if self.get_protocol(protocol).get('kpoints_distance'):
inputdict['kpt'] = {'method': 'auto', 'kptrlen': self.get_protocol(protocol).get('kpoints_distance')}
if psp:
import os
builder.pseudos = orm.List()
psprel = [os.path.normpath(os.path.relpath(i)) for i in psp]
builder.pseudos.extend(psprel)

builder.parameters = BigDFTParameters(dict=inputdict)
builder.code = engines[relaxation_schema]['code']
run_opts = {'options': engines[relaxation_schema]['options']}
Expand Down

0 comments on commit 34ef55f

Please sign in to comment.