inputparsing.py 12.6 KB
Newer Older
1
import numpy as np
2
3
import logging
from collections import defaultdict
4
logger = logging.getLogger("nomad")
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42


#===============================================================================
class CP2KInput(object):
    """The contents of a CP2K simulation including default values and default
    units from the version-specific xml file.
    """

    def __init__(self, root_section):
        self.root_section = root_section

    @staticmethod
    def decode_cp2k_unit(unit):
        """Given a CP2K unit name, decode it as Pint unit definition.
        """
        map = {
            # Length
            "bohr": "bohr",
            "m": "meter",
            "pm": "picometer",
            "nm": "nanometer",
            "angstrom": "angstrom",

            # Angle
            "rad": "radian",
            "deg": "degree",

            #Energy
            "Ry": "rydberg"
        }
        pint_unit = map.get(unit)
        if pint_unit:
            return pint_unit
        else:
            logger.error("Unknown CP2K unit definition '{}'.".format(unit))

    def set_parameter(self, path, value):
        parameter, section = self.get_parameter_and_section(path)
43
44
45
46
47
48
49
50
        if section is None:
            message = "The CP2K input does not contain a section {}".format(path)
            logger.warning(message)
        if parameter is None:
            message = "The CP2K input section {} does not contain a SECTION_PARAMETER".format(path)
            logger.warning(message)
        else:
            parameter.value = value
51
52
53

    def set_keyword(self, path, value):
        keyword, section = self.get_keyword_and_section(path)
54
        # If keyword found, put data in there
55
56
        if keyword and section:
            keyword.value = value
57
        # Keyword not found in the input tree, assuming it is a default keyword
58
59
60
        elif section is not None:
            split_path = path.rsplit("/", 1)
            keyword = split_path[1]
61
62
63
64
65
66
            if section.default_keyword is not None:
                # print "Saving default keyword at path '{}'".format(path)
                section.default_keyword.value += keyword + " " + value + "\n"
            else:
                message = "The CP2K input does not contain the keyword {}, and there is no default keyword for the section {}".format(path, split_path[0])
                logger.warning(message)
67
68
69
70
71
72
73

    def get_section(self, path):
        split_path = path.split("/")
        section = self.root_section
        for part in split_path:
            section = section.get_subsection(part)
            if not section:
74
75
                message = "The CP2K input does not contain the section {}".format(path)
                logger.warning(message)
76
77
78
79
80
81
82
83
                return None
        return section

    def get_keyword_and_section(self, path):
        split_path = path.rsplit("/", 1)
        keyword = split_path[1]
        section_path = split_path[0]
        section = self.get_section(section_path)
84
85
86
87
88
89

        if section is None:
            message = "The CP2K input does not contain the section {}".format(path)
            logger.warning(message)
            return (None, None)

90
91
92
        keyword = section.get_keyword(keyword)
        if keyword and section:
            return (keyword, section)
93
        else:
94
95
96
97
98
99
100
101
102
103
104
105
            return (None, section)

    def get_keyword(self, path):
        """Returns the keyword that is specified by the given path.
        If the keyword has no value set, returns the default value defined in
        the XML.
        """
        keyword, section = self.get_keyword_and_section(path)
        if keyword:
            if keyword.value is not None:
                return keyword.get_value()
            else:
106
                return keyword.default_value
107
108

    def get_default_keyword(self, path):
109
        return self.get_section(path).default_keyword.value
110
111
112

    def set_section_accessed(self, path):
        section = self.get_section(path)
113
114
115
116
117
        if section:
            section.accessed = True
        else:
            message = "The CP2K input does not contain the section {}".format(path)
            logger.warning(message)
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135

    def get_keyword_default(self, path):
        keyword, section = self.get_keyword_and_section(path)
        if keyword:
            return keyword.default_value

    def get_default_unit(self, path):
        keyword, section = self.get_keyword_and_section(path)
        if keyword:
            return keyword.default_unit

    def get_unit(self, path):
        keyword, section = self.get_keyword_and_section(path)
        if keyword:
            return keyword.get_unit()

    def get_parameter_and_section(self, path):
        section = self.get_section(path)
136
137
138
139
140
141
142
        if section is None:
            return (None, None)
        if section.section_parameter is not None:
            parameter = section.section_parameter
            return (parameter, section)
        else:
            return (None, section)
143
144
145
146
147
148
149
150
151
152
153

    def get_parameter(self, path):
        parameter, section = self.get_parameter_and_section(path)
        if parameter:
            if parameter.value:
                return parameter.value
            elif section and section.accessed:
                return parameter.lone_value


#===============================================================================
154
155
class InputObject(object):
    """Base class for all kind of data elements in the CP2K input.
156
    """
157
    __slots__ = ['name', 'value', 'default_value', 'description', 'data_type', 'data_dimension']
158

159
160
    def __init__(self, name):
        self.name = name
161
        self.value = None
162
163
164
165
166
        self.description = None
        self.data_type = None
        self.data_dimension = None
        self.default_value = None

167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184

#===============================================================================
class Keyword(InputObject):
    """Information about a keyword in a CP2K calculation.
    """
    __slots__ = ['unit', 'value_no_unit', 'default_unit', 'default_name']

    def __init__(self, name, default_value,  default_unit, default_name):
        super(Keyword, self).__init__(name)
        self.unit = None
        self.value_no_unit = None
        self.default_unit = default_unit
        self.default_value = default_value
        self.default_name = default_name

    def get_value(self):
        """Returns the value stored in this keyword by removing the possible
        unit definition and formatting the string into the correct data type.
185
        """
186
187
188
189
190
191
192
193
194
        # Decode the unit and the value if not done before
        if self.default_unit:
            if not self.value_no_unit:
                self.decode_cp2k_unit_and_value()
        if self.value_no_unit is not None:
            proper_value = self.value_no_unit
        else:
            proper_value = self.value

195
196
        returned = None
        dim = int(self.data_dimension)
197
        splitted = proper_value.split()
198
199
200
        if len(splitted) != dim:
            logger.error("The dimensions of the CP2K input parameter {} do not match the specification in the XML file.".format(self.name))

201
202
203
        if dim == 1:
            try:
                if self.data_type == "integer":
204
                    returned = int(proper_value)
205
                elif self.data_type == "real":
206
                    returned = float(proper_value)
207
                elif self.data_type == "word":
208
                    returned = str(proper_value)
209
                elif self.data_type == "keyword":
210
                    returned = str(proper_value)
211
                elif self.data_type == "string":
212
                    returned = str(proper_value)
213
                elif self.data_type == "logical":
214
                    returned = str(proper_value)
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
                else:
                    logger.error("Unknown data type '{}'".format(self.data_type))
                    return
            except TypeError:
                logger.error("The CP2K input parameter {} could not be converted to the type specified in the XML file.".format(self.name))
                return
        else:
            try:
                if self.data_type == "integer":
                    returned = np.array([int(x) for x in splitted])
                elif self.data_type == "real":
                    returned = np.array([float(x) for x in splitted])
                elif self.data_type == "word":
                    returned = np.array([str(x) for x in splitted])
                elif self.data_type == "keyword":
                    returned = np.array([str(x) for x in splitted])
                elif self.data_type == "string":
                    returned = np.array([str(x) for x in splitted])
                elif self.data_type == "logical":
                    returned = np.array([str(x) for x in splitted])
                else:
                    logger.error("Unknown data type '{}'".format(self.data_type))
                    return
            except TypeError:
                logger.error("The CP2K input parameter {} could not be converted to the type specified in the XML file.".format(self.name))
240
241
242
                return

        return returned
243

244
    def determine_value_and_unit(self):
245
246
247
248
249
250
251
252
253
254
255
        """If the units of this value can be changed, return a value and the
        unit separately.
        """
        if self.default_unit:
            if not self.value_no_unit:
                self.decode_cp2k_unit_and_value()
            return self.value_no_unit
        else:
            return self.value

    def get_unit(self):
256
257

        # Decode the unit and the value if not done before
258
259
260
261
262
263
        if self.default_unit:
            if not self.unit:
                self.decode_cp2k_unit_and_value()
            return self.unit
        else:
            logger.error("The keyword '{}' does not have a unit.".format(self.default_name))
264
            return None
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284

    def decode_cp2k_unit_and_value(self):
        """Given a CP2K unit name, decode it as Pint unit definition.
        """
        splitted = self.value.split(None, 1)
        unit_definition = splitted[0]
        if unit_definition.startswith('[') and unit_definition.endswith(']'):
            unit_definition = unit_definition[1:-1]
            self.unit = CP2KInput.decode_cp2k_unit(self.default_unit)
            self.value_no_unit = splitted[1]
        elif self.default_unit:
            logger.debug("No special unit definition found, returning default unit.")
            self.unit = CP2KInput.decode_cp2k_unit(self.default_unit)
            self.value_no_unit = self.value
        else:
            logger.debug("The value has no unit, returning bare value.")
            self.value_no_unit = self.value


#===============================================================================
285
class Section(object):
286
287
    """An input section in a CP2K calculation.
    """
288
    __slots__ = ['accessed', 'name', 'keywords', 'default_keyword_names', 'default_keyword', 'section_parameter', 'sections', 'description']
289
290
291
292
293

    def __init__(self, name):
        self.accessed = False
        self.name = name
        self.keywords = defaultdict(list)
294
295
        self.default_keyword_names = []
        self.default_keyword = None
296
297
        self.section_parameter = None
        self.sections = defaultdict(list)
298
        self.description = None
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319

    def get_keyword(self, name):
        keyword = self.keywords.get(name)
        if keyword:
            if len(keyword) == 1:
                return keyword[0]
            else:
                logger.error("The keyword '{}' in '{}' does not exist or has too many entries.".format(name, self.name))

    def get_subsection(self, name):
        subsection = self.sections.get(name)
        if subsection:
            if len(subsection) == 1:
                return subsection[0]
            else:
                logger.error("The subsection '{}' in '{}' has too many entries.".format(name, self.name))
        else:
            logger.error("The subsection '{}' in '{}' does not exist.".format(name, self.name))


#===============================================================================
320
class SectionParameters(InputObject):
321
322
323
324
325
    """Section parameters in a CP2K calculation.

    Section parameters are the short values that can be added right after a
    section name, e.g. &PRINT ON, where ON is the section parameter.
    """
326
    __slots__ = ['lone_keyword_value']
327

328
329
    def __init__(self, default_value, lone_keyword_value):
        super(SectionParameters, self).__init__("SECTION_PARAMETERS")
330
        self.default_value = default_value
331
332
333
334
335
336
337
        self.lone_keyword_value = lone_keyword_value


#===============================================================================
class DefaultKeyword(InputObject):
    """Default keyword in the CP2K input.
    """
338
339
    __slots__ = ['lone_value']

340
341
342
343
    def __init__(self):
        super(DefaultKeyword, self).__init__("DEFAULT_KEYWORD")
        self.lone_value = None
        self.value = ""