artificial.py 8.34 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Copyright 2018 Markus Scheidgen
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an"AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Parser for creating artificial test, brenchmark, and demonstration data.
"""

import json
import os.path
import numpy as np
22
23
24
import random
from ase.data import chemical_symbols
import numpy
25
26
import sys
import time
27
28
import os
import signal
29
30

from nomadcore.local_meta_info import loadJsonFile, InfoKindEl
31
import nomad_meta_info
32
33
34
35
36

from nomad.parsing.backend import LocalBackend
from nomad.parsing.parser import Parser


37
38
class ArtificalParser(Parser):
    """ Base class for artifical parsers based on VASP metainfo. """
39
40
41
    def __init__(self):
        super().__init__()
        # use vasp metainfo, not to really use it, but because it works
42
43
        file_dir = os.path.dirname(os.path.abspath(nomad_meta_info.__file__))
        meta_info_path = os.path.normpath(os.path.join(file_dir, 'vasp.nomadmetainfo.json'))
44
45
46
        self.meta_info_env, _ = loadJsonFile(filePath=meta_info_path, dependencyLoader=None, extraArgsHandling=InfoKindEl.ADD_EXTRA_ARGS, uri=None)
        self.backend = None

47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
    def init_backend(self):
        self.backend = LocalBackend(metaInfoEnv=self.meta_info_env, debug=False)

    @property
    def name(self):
        return self.__class__.name


class TemplateParser(ArtificalParser):
    """
    A parser that generates data based on a template given via the
    mainfile. The template is basically some archive json. Only
    """
    name = 'parsers/template'

62
    def is_mainfile(self, filename: str, mime: str, buffer: str, compression: str = None) -> bool:
63
64
        return filename.endswith('template.json')

65
66
67
    def transform_value(self, name, value):
        """ allow subclasses to modify values """
        return value
68

69
70
71
72
73
    def transform_section(self, name, section):
        """ allow subclasses to modify sections """
        return section

    def add_section(self, section):
74
75
76
77
78
79
80
81
82
83
        name = section['_name']
        index = self.backend.openSection(name)

        for key, value in section.items():
            if key.startswith('x_') or key.startswith('_'):
                continue

            if key.startswith('section_'):
                values = value if isinstance(value, list) else [value]
                for value in values:
84
                    self.add_section(self.transform_section(key, value))
85
            else:
86
                value = self.transform_value(key, value)
87
88
89
90
91
92
93
94
95
96
97
98
                if isinstance(value, list):
                    shape = self.meta_info_env[key].get('shape')
                    if shape is None or len(shape) == 0:
                        for single_value in value:
                            self.backend.addValue(key, single_value, index)
                    else:
                        self.backend.addArrayValues(key, np.asarray(value), index)
                else:
                    self.backend.addValue(key, value, index)

        self.backend.closeSection(name, index)

99
    def run(self, mainfile: str, logger=None) -> LocalBackend:
100
101
102
103
        # tell tests about received logger
        if logger is not None:
            logger.debug('received logger')

104
        self.init_backend()
105
106
107
108

        if 'warning' in mainfile:
            self.backend.pwarn('A test warning.')

109
110
111
112
        template_json = json.load(open(mainfile, 'r'))
        section = template_json['section_run'][0]
        self.add_section(section)
        self.backend.finishedParsingSession('ParseSuccess', [])
113
        logger.debug('a test log entry')
114
        return self.backend
115
116


117
118
119
120
121
122
123
124
class ChaosParser(ArtificalParser):
    """
    Parser that emulates typical error situations. Files can contain a json string (or
    object with key `chaos`) with one of the following string values:
    - exit
    - deadlock
    - consume_ram
    - exception
125
    - segfault
126
127
128
129
    - random
    """
    name = 'parsers/chaos'

130
    def is_mainfile(self, filename: str, mime: str, buffer: str, compression: str = None) -> bool:
131
132
133
134
135
136
137
138
139
140
141
142
143
144
        return filename.endswith('chaos.json')

    def run(self, mainfile: str, logger=None) -> LocalBackend:
        self.init_backend()

        chaos_json = json.load(open(mainfile, 'r'))
        if isinstance(chaos_json, str):
            chaos = chaos_json
        elif isinstance(chaos_json, dict):
            chaos = chaos_json.get('chaos', None)
        else:
            chaos = None

        if chaos == 'random':
145
            chaos = random.choice(['exit', 'deadlock', 'consume_ram', 'exception', 'segfault'])
146
147
148
149
150
151
152

        if chaos == 'exit':
            sys.exit(1)
        elif chaos == 'deadlock':
            while True:
                time.sleep(1)
        elif chaos == 'consume_ram':
153
154
155
            data = []
            while True:
                data.append('a' * 10**6)
156
157
        elif chaos == 'exception':
            raise Exception('Some chaos happened, muhuha...')
158
159
        elif chaos == 'segfault':
            os.kill(os.getpid(), signal.SIGSEGV)
160
161
162
163

        raise Exception('Unknown chaos')


164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
class GenerateRandomParser(TemplateParser):
    name = 'parsers/random'

    basis_set_types = [
        'Numeric AOs', 'Gaussians', '(L)APW+lo', 'FLAPW', 'Plane waves',
        'Real-space grid', 'Local-orbital minimum-basis']

    electronic_structure_methods = [
        'DFT', 'DFT+U', 'full-CI', 'CIS', 'CISD'
        'CCS', 'CCS(D)', 'CCSD', 'CCSD(T)', 'CCSDT(Q)', 'MP2', 'MP3', 'MP4', 'MP5', 'MP6',
        'G0W0', 'scGW', 'LDA', 'hybrid', 'CASPT2', 'MRCIS', 'MRCISD', 'RAS-CI']

    XC_functional_names = [
        'LDA_X', 'LDA_C', 'GGA_X', 'GGA_C', 'HYB_GGA_XC', 'MGGA_X', 'MGGA_C', 'HYB_MGGA_XC']

    low_numbers = [1, 1, 2, 2, 2, 2, 2, 3, 3, 4]

    def __init__(self):
        super(GenerateRandomParser, self).__init__()
        file_dir = os.path.dirname(os.path.abspath(__file__))
        relative_template_file = "random_template.json"
        template_file = os.path.normpath(os.path.join(file_dir, relative_template_file))
        self.template = json.load(open(template_file, 'r'))
        self.random = None

189
    def is_mainfile(self, filename: str, mime: str, buffer: str, compression: str = None) -> bool:
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
        return os.path.basename(filename).startswith('random_')

    def transform_section(self, name, section):
        """ allow subclasses to modify sections """
        if name == 'section_system':
            atoms = []
            atom_positions = []
            # different atoms
            for _ in range(0, random.choice(GenerateRandomParser.low_numbers)):
                # ase data starts with a place holder value X, we don't want that
                atom = random.choice(chemical_symbols[1:])
                # atoms of the same number
                for _ in range(0, random.choice(GenerateRandomParser.low_numbers)):
                    atoms.append(atom)
                    atom_positions.append([.0, .0, .0])

            section['atom_labels'] = atoms
            section['atom_positions'] = atom_positions
            return section
        else:
            return section

    def transform_value(self, name, value):
        if name == 'program_basis_set_type':
            return random.choice(GenerateRandomParser.basis_set_types)
        elif name == 'electronic_structure_method':
            return random.choice(GenerateRandomParser.electronic_structure_methods)
        elif name == 'XC_functional_name':
            return random.choice(GenerateRandomParser.XC_functional_names)
        elif name == 'atom_positions':
            return [numpy.random.uniform(0e-10, 1e-9, 3) for _ in value]
        else:
            return value

224
    def run(self, mainfile: str, logger=None) -> LocalBackend:
225
226
227
228
        # tell tests about received logger
        if logger is not None:
            logger.debug('received logger')

229
230
231
        self.init_backend()
        seed = int(os.path.basename(mainfile).split('_')[1])
        random.seed(seed)
232
        numpy.random.seed(seed)
233
234
235
236
        section = self.template['section_run'][0]
        self.add_section(section)
        self.backend.finishedParsingSession('ParseSuccess', [])
        return self.backend