optimade.py 5.22 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 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.

15
from typing import Any, Dict
16
import numpy as np
17
18
import re
import ase.data
19
from string import ascii_uppercase
20
import pint.quantity
21
22

from nomad.normalizing.normalizer import SystemBasedNormalizer
23
from nomad.units import ureg
24
from nomad.datamodel import OptimadeEntry, Species, DFTMetadata, EntryMetadata
25
from nomad.datamodel.metainfo.public import section_system
26
27

species_re = re.compile(r'^([A-Z][a-z]?)(\d*)$')
28
29
30
31


class OptimadeNormalizer(SystemBasedNormalizer):

32
    '''
33
34
    This normalizer performs all produces a section all data necessary for the Optimade API.
    It assumes that the :class:`SystemNormalizer` was run before.
35
    '''
Markus Scheidgen's avatar
Markus Scheidgen committed
36
37
    def __init__(self, archive):
        super().__init__(archive, only_representatives=True)
38

39
    def add_optimade_data(self, index) -> OptimadeEntry:
40
        '''
41
42
43
        The 'main' method of this :class:`SystemBasedNormalizer`.
        Normalizes the section with the given `index`.
        Normalizes geometry, classifies, system_type, and runs symmetry analysis.
44
        '''
45

46
47
48
49
50
        if self.entry_archive.section_metadata is None:
            self.entry_archive.m_create(EntryMetadata)
        if self.entry_archive.section_metadata.dft is None:
            self.entry_archive.section_metadata.m_create(DFTMetadata)
        optimade = self.entry_archive.section_metadata.dft.m_create(OptimadeEntry)
51

52
        def get_value(quantity_def, default: Any = None, numpy: bool = False, unit=None) -> Any:
53
            try:
54
                value = self.section_run.section_system[-1].m_get(quantity_def)
55
56
57
58
59
                if type(value) == np.ndarray and not numpy:
                    return value.tolist()
                if isinstance(value, list) and numpy:
                    return np.array(value)

60
61
62
63
64
65
                if numpy and unit is not None:
                    if isinstance(value, pint.quantity._Quantity):
                        value = value.to(unit)
                    else:
                        value = value * unit

66
67
68
69
70
71
                return value
            except KeyError:
                return default

        from nomad.normalizing.system import normalized_atom_labels

72
        nomad_species = get_value(section_system.atom_labels)
73
74
75

        # elements
        atoms = normalized_atom_labels(nomad_species)
76
        atom_count = len(atoms)
77
78
79
80
81
82
83
84
85
86
        atom_counts: Dict[str, int] = {}
        for atom in atoms:
            current = atom_counts.setdefault(atom, 0)
            current += 1
            atom_counts[atom] = current

        optimade.elements = list(set(atoms))
        optimade.elements.sort()
        optimade.nelements = len(optimade.elements)
        optimade.elements_ratios = [
87
            atom_counts[element] / atom_count
88
89
90
            for element in optimade.elements]

        # formulas
91
92
        optimade.chemical_formula_reduced = get_value(section_system.chemical_composition_reduced)
        optimade.chemical_formula_hill = get_value(section_system.chemical_composition_bulk_reduced)
93
        optimade.chemical_formula_descriptive = optimade.chemical_formula_hill
94
95
96
97
98
99
        optimade.chemical_formula_anonymous = ''
        for i in range(len(optimade.elements)):
            part = '%s' % ascii_uppercase[i % len(ascii_uppercase)]
            if atom_counts[optimade.elements[i]] > 1:
                part += str(atom_counts[optimade.elements[i]])
            optimade.chemical_formula_anonymous += part
100
101
102
103

        # sites
        optimade.nsites = len(nomad_species)
        optimade.species_at_sites = nomad_species
104
105
        optimade.lattice_vectors = get_value(section_system.lattice_vectors, numpy=True, unit=ureg.m)
        optimade.cartesian_site_positions = get_value(section_system.atom_positions, numpy=True, unit=ureg.m)
106
107
        optimade.dimension_types = [
            1 if value else 0
108
            for value in get_value(section_system.configuration_periodic_dimensions)]
109

110
111
112
113
        # species
        for species_label in set(nomad_species):
            match = re.match(species_re, species_label)

114
            element_label = match.group(1)
115
116
117
118
119
120
121
122
123
124
125

            species = optimade.m_create(Species)
            species.name = species_label
            if element_label in ase.data.chemical_symbols:
                chemical_symbol = element_label
            else:
                chemical_symbol = 'x'
            species.chemical_symbols = [chemical_symbol]
            species.concentration = [1.0]

        optimade.structure_features = []
126
127
128

        return optimade

129
    def normalize_system(self, system, is_representative):
130
131
132
        if not is_representative:
            return False

133
        try:
134
            self.add_optimade_data(system.m_parent_index)
135
136
            return True

137
138
        except Exception as e:
            self.logger.warn('could not acquire optimade data', exc_info=e)