Commit 281183e0 authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

Added springer and aflow prototype as labels to the repo data. #259

parent caebe599
Pipeline #68687 canceled with stages
in 3 minutes and 22 seconds
......@@ -18,8 +18,7 @@ from elasticsearch_dsl import Keyword
from collections.abc import Mapping
import numpy as np
from nomad import utils, config
from nomad.metainfo import MSection
from nomad import config
from nomad import utils, config
from .metainfo import Dataset, User
......@@ -120,9 +119,6 @@ class CalcWithMetadata(Mapping):
if value is None or key in ['backend']:
raise KeyError()
if isinstance(value, MSection):
value = value.m_to_dict()
return value
def __iter__(self):
......@@ -152,15 +148,6 @@ class CalcWithMetadata(Mapping):
if value is None:
continue
if isinstance(value, list):
if len(value) == 0:
continue
if len(value) > 0 and isinstance(value[0], dict) and not isinstance(value[0], utils.POPO):
value = list(utils.POPO(**item) for item in value)
if isinstance(value, dict) and not isinstance(value, utils.POPO):
value = utils.POPO(**value)
setattr(self, key, value)
def apply_user_metadata(self, metadata: dict):
......
......@@ -18,13 +18,14 @@ DFT specific metadata
from typing import List
import re
from elasticsearch_dsl import Integer, Object
from elasticsearch_dsl import Integer, Object, InnerDoc
import ase.data
from nomadcore.local_backend import ParserEvent
from nomad import utils, config
from nomad.metainfo import optimade
from nomad.metainfo import optimade, MSection, Section, Quantity, MEnum
from nomad.metainfo.elastic import elastic_mapping, elastic_obj
from .base import CalcWithMetadata, DomainQuantity, Domain, get_optional_backend_value
......@@ -69,6 +70,26 @@ def simplify_version(version):
return match.group(0)
class Label(MSection):
"""
Label that further classify a structure.
Attributes:
label: The label as a string
type: The type of the label
source: The source that this label was taken from.
"""
m_def = Section(a_elastic=dict(type=InnerDoc))
label = Quantity(type=str)
type = Quantity(type=MEnum('compound_class', 'classification', 'prototype', 'prototype_id'))
source = Quantity(type=MEnum('springer', 'aflow_prototype_library'))
ESLabel = elastic_mapping(Label.m_def, InnerDoc)
class DFTCalcWithMetadata(CalcWithMetadata):
def __init__(self, **kwargs):
......@@ -92,6 +113,7 @@ class DFTCalcWithMetadata(CalcWithMetadata):
self.geometries = []
self.group_hash: str = None
self.labels: List[Label] = []
self.optimade: optimade.OptimadeEntry = None
super().__init__(**kwargs)
......@@ -99,15 +121,30 @@ class DFTCalcWithMetadata(CalcWithMetadata):
def update(self, **kwargs):
super().update(**kwargs)
if len(self.labels) > 0:
self.labels = [Label.m_from_dict(label) for label in self.labels]
if self.optimade is not None and isinstance(self.optimade, dict):
self.optimade = optimade.OptimadeEntry.m_from_dict(self.optimade)
def __getitem__(self, key):
value = super().__getitem__(key)
if key == 'labels':
return [item.m_to_dict() for item in value]
if key == 'optimade':
return value.m_to_dict()
return value
def apply_domain_metadata(self, backend):
from nomad.normalizing.system import normalized_atom_labels
logger = utils.get_logger(__name__).bind(
upload_id=self.upload_id, calc_id=self.calc_id, mainfile=self.mainfile)
# code and code specific ids
self.code_name = backend.get_value('program_name', 0)
try:
self.code_version = simplify_version(backend.get_value('program_version', 0))
......@@ -116,6 +153,7 @@ class DFTCalcWithMetadata(CalcWithMetadata):
self.raw_id = get_optional_backend_value(backend, 'raw_id', 'section_run', 0)
# metadata (system, method, chemistry)
self.atoms = get_optional_backend_value(backend, 'atom_labels', 'section_system', [], logger=logger)
if hasattr(self.atoms, 'tolist'):
self.atoms = self.atoms.tolist()
......@@ -138,6 +176,7 @@ class DFTCalcWithMetadata(CalcWithMetadata):
self.xc_functional = map_functional_name_to_xc_treatment(
get_optional_backend_value(backend, 'XC_functional_name', 'section_method', logger=logger))
# grouping
self.group_hash = utils.hash(
self.formula,
self.spacegroup,
......@@ -151,6 +190,7 @@ class DFTCalcWithMetadata(CalcWithMetadata):
self.uploader,
self.coauthors)
# metrics and quantities
quantities = set()
geometries = set()
n_quantities = 0
......@@ -184,6 +224,26 @@ class DFTCalcWithMetadata(CalcWithMetadata):
self.n_total_energies = n_total_energies
self.n_geometries = n_geometries
# labels
compounds = set()
classifications = set()
for index in backend.get_sections('section_springer_material'):
compounds.update(backend.get_value('springer_compound_class', index))
classifications.update(backend.get_value('springer_classification', index))
for compound in compounds:
self.labels.append(Label(label=compound, type='compound_class', source='springer'))
for classification in classifications:
self.labels.append(Label(label=classification, type='classification', source='springer'))
aflow_id = get_optional_backend_value(backend, 'prototype_aflow_id', 'section_prototype')
aflow_label = get_optional_backend_value(backend, 'prototype_label', 'section_prototype')
if aflow_id is not None and aflow_label is not None:
self.labels.append(Label(label=aflow_label, type='prototype', source='aflow_prototype_library'))
self.labels.append(Label(label=aflow_id, type='prototype_id', source='aflow_prototype_library'))
# optimade
self.optimade = backend.get_mi2_section(optimade.OptimadeEntry.m_def)
......@@ -240,10 +300,16 @@ Domain(
n_atoms=DomainQuantity(
'Number of atoms in the simulated system',
elastic_mapping=Integer()),
labels=DomainQuantity(
'Search based for springer classification and aflow prototypes',
elastic_field='labels.label',
elastic_mapping=Object(ESLabel),
elastic_value=lambda labels: [elastic_obj(label, ESLabel) for label in labels],
multi=True),
optimade=DomainQuantity(
'Search based on optimade\'s filter query language',
elastic_mapping=Object(optimade.ESOptimadeEntry),
elastic_value=lambda entry: optimade.elastic_obj(entry, optimade.ESOptimadeEntry)
elastic_value=lambda entry: elastic_obj(entry, optimade.ESOptimadeEntry)
)),
metrics=dict(
total_energies=('n_total_energies', 'sum'),
......
# 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.
"""
Adds elastic search support to the metainfo.
"""
from . import Section, MSection
def elastic_mapping(section: Section, base_cls: type) -> type:
""" Creates an elasticsearch_dsl document class from a section definition. """
dct = {
name: quantity.m_annotations['elastic']['type']()
for name, quantity in section.all_quantities.items()
if 'elastic' in quantity.m_annotations}
return type(section.name, (base_cls,), dct)
def elastic_obj(source: MSection, target_cls: type):
if source is None:
return None
assert isinstance(source, MSection), '%s must be an MSection decendant' % source.__class__.__name__
target = target_cls()
for name, quantity in source.m_def.all_quantities.items():
elastic_annotation = quantity.m_annotations.get('elastic')
if elastic_annotation is None:
continue
if 'mapping' in elastic_annotation:
value = elastic_annotation['mapping'](source)
else:
value = getattr(source, name)
setattr(target, name, value)
return target
......@@ -2,7 +2,8 @@ from ase.data import chemical_symbols
from elasticsearch_dsl import Keyword, Integer, Float, InnerDoc, Nested
import numpy as np
from nomad.metainfo import MSection, Section, Quantity, SubSection, MEnum, units
from . import MSection, Section, Quantity, SubSection, MEnum, units
from .elastic import elastic_mapping
def optimade_links(section: str):
......@@ -233,38 +234,4 @@ class OptimadeEntry(MSection):
species = SubSection(sub_section=Species.m_def, repeats=True)
def elastic_mapping(section: Section, base_cls: type) -> type:
""" Creates an elasticsearch_dsl document class from a section definition. """
dct = {
name: quantity.m_annotations['elastic']['type']()
for name, quantity in section.all_quantities.items()
if 'elastic' in quantity.m_annotations}
return type(section.name, (base_cls,), dct)
def elastic_obj(source: MSection, target_cls: type):
if source is None:
return None
assert isinstance(source, MSection)
target = target_cls()
for name, quantity in source.m_def.all_quantities.items():
elastic_annotation = quantity.m_annotations.get('elastic')
if elastic_annotation is None:
continue
if 'mapping' in elastic_annotation:
value = elastic_annotation['mapping'](source)
else:
value = getattr(source, name)
setattr(target, name, value)
return target
ESOptimadeEntry = elastic_mapping(OptimadeEntry.m_def, InnerDoc)
......@@ -1078,6 +1078,14 @@ class TestRepo():
data = json.loads(rv.data)
assert data['pagination']['total'] > 0
def test_label(self, api, non_empty_processed, test_user_auth):
rv = api.get(
'/repo/?%s' % urlencode(dict(owner='all', label=['oxide', 'metal']), doseq=True),
headers=test_user_auth)
assert rv.status_code == 200
data = json.loads(rv.data)
assert data['pagination']['total'] > 0
def test_get_code_from_query(self, api, example_elastic_calcs, test_user_auth):
rv = api.get('/repo/?code_name=VASP', headers=test_user_auth)
assert rv.status_code == 200
......
......@@ -86,7 +86,6 @@ class TestAdmin:
result = click.testing.CliRunner().invoke(
cli, ['admin', 'entries', 'rm', calc.calc_id], catch_exceptions=False, obj=utils.POPO())
print(result.output)
assert result.exit_code == 0
assert 'deleting' in result.stdout
assert Upload.objects(upload_id=upload_id).first() is not None
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment