basisset.py 6.65 KB
Newer Older
1
from abc import ABC, abstractmethod
2
3
4
5
from collections import OrderedDict
import numpy as np
from typing import Tuple, List

6
from nomad.parsing.backend import LocalBackend
7
from nomad.parsing.backend import Section
8
from nomad.utils import RestrictedDict
9

10

11
def get_basis_set(context, backend: LocalBackend, logger) -> RestrictedDict:
12
13
14
15
16
17
18
19
20
21
22
23
    """Decide which type of basis set settings are applicable to the entry and
    return a corresponding settings as a RestrictedDict.

    Args:
        context: The calculation context.
        backend: Backend from which values are extracted.
        logger: Shared logger.

    Returns:
        RestrictedDict or None: Returns the extracted settings as a
        RestrictedDict. If no suitable basis set settings could be identified,
        returns None.
24
    """
25
    settings: BasisSet = None
26
27
    program_name = backend.get('program_name')
    if program_name == "exciting":
28
        settings = BasisSetExciting(context, backend, logger)
29
    elif program_name == "FHI-aims":
30
        settings = BasisSetFHIAims(context, backend, logger)
31
32
    else:
        return None
33

34
    return settings.to_dict()
35
36


37
class BasisSet(ABC):
38
39
40
    """Abstract base class for basis set settings. The idea is to create
    subclasses that inherit this class and hierarchically add new mandatory and
    optional settings with the setup()-function.
41
42
43
44
    """
    def __init__(self, context, backend, logger):
        """
        """
45
46
47
48
        self._ctx = context
        self._backend = backend
        self._logger = logger
        mandatory, optional = self.setup()
49
        self.settings = RestrictedDict(mandatory, optional, forbidden_values=[None])
50

51
    @abstractmethod
52
53
54
    def to_dict(self) -> RestrictedDict:
        """Used to extract basis set settings from the backend and returning
        them as a RestrictedDict.
55
        """
56
        pass
57
58

    @abstractmethod
59
60
    def setup(self) -> Tuple:
        """Used to define a list of mandatory and optional settings for a
61
        subclass.
62

63
64
65
        Returns:
            Should return a tuple of two lists: the first one defining
            mandatory keys and the second one defining optional keys.
66
        """
67
68
69
        mandatory: List = []
        optional: List = []
        return mandatory, optional
70
71


72
class BasisSetFHIAims(BasisSet):
73
    """Basis set settings for 'FHI-Aims' (code-dependent).
74
    """
75
    def setup(self) -> Tuple:
76
        # Get previously defined values from superclass
77
        mandatory, optional = super().setup()
78
79
80
81

        # Add new values
        mandatory += ["fhiaims_basis"]

82
        return mandatory, optional
83

84
    def to_dict(self):
85
        # Get basis set settings for each species
86
87
88
89
90
91
92
93
94
95
96
97
98
        aims_bs = self._backend.get('x_fhi_aims_section_controlIn_basis_set')
        if aims_bs is not None:
            bs_by_species = {}
            for this_aims_bs in aims_bs:
                this_bs_dict = self._values_to_dict(this_aims_bs, level=2)
                this_species = this_aims_bs['x_fhi_aims_controlIn_species_name'][0]
                bs_by_species[this_species] = this_bs_dict

            # Sort alphabetically by species label
            if bs_by_species:
                basis = OrderedDict()
                for k in sorted(bs_by_species.keys()):
                    basis[k] = bs_by_species[k]
99
100
101
                self.settings["fhiaims_basis"] = basis

        return self.settings
102
103
104
105
106
107

    @classmethod
    def _values_to_dict(cls, data, level=0):
        result = None
        if data is None:
            return None
108
        elif isinstance(data, (Section, dict)):
109
110
111
112
113
114
115
116
117
            result = OrderedDict()
            for k in sorted(cls._filtered_section_keys(data)):
                v = data.get(k, None)
                result[k] = cls._values_to_dict(v, level=level + 1)
        elif isinstance(data, (list)):
            result = []
            for k in range(len(data)):
                v = data[k]
                result.append(cls._values_to_dict(v, level=level + 1))
118
        elif isinstance(data, (np.ndarray)):
119
120
121
122
123
            result = data.tolist()
        else:
            result = data
        return result

124
125
126
127
128
129
130
131
132
133
134
135
    @classmethod
    def _filtered_section_keys(cls, section):
        for k in section.keys():
            # skip JSON-specific keys
            if k == '_gIndex':
                continue
            if k == '_name':
                continue
            else:
                # json values and subsections
                yield k

136

137
class BasisSetExciting(BasisSet):
138
    """Basis set settings for 'Exciting' (code-dependent).
139
    """
140
    def setup(self) -> Tuple:
141
        # Get previously defined values from superclass
142
        mandatory, optional = super().setup()
143
144

        # Add new values
145
146
147
148
149
150
151
        mandatory += [
            "muffin_tin_settings",
            "rgkmax",
            "gkmax",
            "lo",
            "lmaxapw",
        ]
152

153
154
        return mandatory, optional

155
    def to_dict(self):
156
157
158
        """Special case of basis set settings for Exciting code. See list at:
        https://gitlab.mpcdf.mpg.de/nomad-lab/encyclopedia-general/wikis/FHI-visit-preparation
        """
159
        # Add the muffin-tin settings for each species ordered alphabetically by atom label
160
        try:
161
            groups = self._backend["x_exciting_section_atoms_group"]
162
            groups = sorted(groups, key=lambda group: group["x_exciting_geometry_atom_labels"])
163
            muffin_tin_settings = OrderedDict()
164
165
166
            for group in groups:
                label = group["x_exciting_geometry_atom_labels"]
                try:
167
                    muffin_tin_settings["{}_muffin_tin_radius".format(label)] = "%.6f" % (1e+10 * group['x_exciting_muffin_tin_radius'])
168
                except KeyError:
169
                    muffin_tin_settings["{}_muffin_tin_radius".format(label)] = None
170
                try:
171
                    muffin_tin_settings["{}_muffin_tin_points".format(label)] = "%d" % group['x_exciting_muffin_tin_points']
172
                except KeyError:
173
174
                    muffin_tin_settings["{}_muffin_tin_points".format(label)] = None
            self.settings["muffin_tin_settings"] = muffin_tin_settings
175
176
        except KeyError:
            pass
177

178
        # Other important method settings
179
        system = self._ctx.representative_system
180
        try:
181
182
183
            self.settings['rgkmax'] = "%.6f" % (system['x_exciting_rgkmax'])
        except KeyError:
            pass
184
        try:
185
186
187
            self.settings['gkmax'] = "%.6f" % (1e-10 * system['x_exciting_gkmax'])
        except KeyError:
            pass
188
        try:
189
190
191
            self.settings['lo'] = "%d" % (system['x_exciting_lo'])
        except KeyError:
            pass
192
        try:
193
194
195
            self.settings['lmaxapw'] = "%d" % (system['x_exciting_lmaxapw'])
        except KeyError:
            pass
196
197

        return self.settings