Commit e537525e authored by Lauri Himanen's avatar Lauri Himanen
Browse files

Moved encyclopedia for now inside section_metadata, first API route functioning.

parent 3fa3910a
Subproject commit 4674d1ad944d181aab12695503154d839f40b5b0
Subproject commit 33098d6a162461dab7afdc8aa822ee9bb6be61bc
......@@ -25,4 +25,4 @@ There is a separate documentation for the API endpoints from a client perspectiv
'''
from .api import api, blueprint
from . import info, auth, upload, repo, archive, raw, mirror, dataset
from . import info, auth, upload, repo, archive, encyclopedia, raw, mirror, dataset
# 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.
'''
The encyclopedia API of the nomad@FAIRDI APIs.
'''
from flask_restplus import Resource, abort, fields
from elasticsearch_dsl import Search
from .api import api
from .auth import authenticate
from nomad import config
ns = api.namespace('encyclopedia', description='Access encyclopedia metadata.')
search = Search(index=config.elastic.index_name)
@ns.route('/materials/<string:material_id>')
class EncMaterialResource(Resource):
@api.response(404, 'The material does not exist')
@api.response(401, 'Not authorized to access the material')
@api.response(200, 'Metadata send', fields.Raw)
@api.doc('get_enc_material')
@authenticate()
def get(self, material_id):
"""Used to retrive basic information related to the specified material.
"""
def add_result(result, key, function, default=""):
try:
value = function()
except Exception:
value = default
result[key] = value
# Find the first entry with this material id and take information from
# there. In principle all other entries should have the same
# information.
s = search.query('term', encyclopedia__material__material_id=material_id)
response = s.execute()
if len(response) == 0:
abort(404, message='There is no material {}'.format(material_id))
entry = response[0]
# Create result JSON
result = {}
result["material_id"] = material_id
add_result(result, "bravais_lattice", lambda: entry.encyclopedia.material.bulk.bravais_lattice, ""),
add_result(result, "crystal_system", lambda: entry.encyclopedia.material.bulk.crystal_system, "")
add_result(result, "formula", lambda: entry.encyclopedia.material.formula, "")
add_result(result, "formula_reduced", lambda: entry.encyclopedia.material.formula_reduced, "")
add_result(result, "material_name", lambda: entry.encyclopedia.material.material_name, "")
add_result(result, "point_group", lambda: entry.encyclopedia.material.bulk.point_group, "")
add_result(result, "space_group", lambda: entry.encyclopedia.material.bulk.space_group_number, "")
add_result(result, "structure_type", lambda: entry.encyclopedia.material.bulk.structure_type, "")
add_result(result, "system_type", lambda: entry.encyclopedia.material.material_type, "")
return result, 200
......@@ -20,13 +20,13 @@ from elasticsearch_dsl import Keyword, Text, analyzer, tokenizer
import ase.data
from nomad import metainfo, config
from nomad.metainfo.encyclopedia import section_encyclopedia
from nomad.metainfo.search_extension import Search
from nomad.metainfo.elastic_extension import ElasticDocument
from nomad.metainfo.mongoengine_extension import Mongo, MongoDocument
from .dft import DFTMetadata
from .ems import EMSMetadata
from .encyclopedia import EncyclopediaMetadata
from .metainfo.public import section_run
from .metainfo.general_experimental import section_experiment
......@@ -449,12 +449,15 @@ class EntryMetadata(metainfo.MSection):
ems = metainfo.SubSection(sub_section=EMSMetadata, a_search='ems')
dft = metainfo.SubSection(sub_section=DFTMetadata, a_search='dft')
encyclopedia = metainfo.SubSection(sub_section=EncyclopediaMetadata, a_search='encyclopedia')
def apply_user_metadata(self, metadata: dict):
''' Applies a user provided metadata dict to this calc. '''
self.m_update(**metadata)
def apply_domain_metadata(self, backend):
"""Used to apply metadata that is related to the domain.
"""
assert self.domain is not None, 'all entries must have a domain'
domain_sub_section_def = self.m_def.all_sub_sections.get(self.domain)
domain_section_def = domain_sub_section_def.sub_section
......@@ -473,7 +476,6 @@ class EntryArchive(metainfo.MSection):
section_run = metainfo.SubSection(sub_section=section_run, repeats=True)
section_experiment = metainfo.SubSection(sub_section=section_experiment)
section_metadata = metainfo.SubSection(sub_section=EntryMetadata)
section_encyclopedia = metainfo.SubSection(sub_section=section_encyclopedia)
processing_logs = metainfo.Quantity(
type=Any, shape=['0..*'],
......
import numpy as np
from elasticsearch_dsl import InnerDoc
from nomad.metainfo import MSection, Section, SubSection, Quantity, MEnum, Reference
from nomad.datamodel.metainfo.public import section_k_band, section_dos, section_thermodynamical_properties
from nomad.metainfo.search_extension import Search
class WyckoffVariables(MSection):
m_def = Section(
a_flask=dict(skip_none=True),
a_elastic=dict(type=InnerDoc),
description="""
Contains the variables associated with a Wyckoff set.
"""
......@@ -35,7 +34,6 @@ class WyckoffVariables(MSection):
class WyckoffSet(MSection):
m_def = Section(
a_flask=dict(skip_none=True),
a_elastic=dict(type=InnerDoc),
description="""
Section for storing Wyckoff set information.
"""
......@@ -65,7 +63,6 @@ class WyckoffSet(MSection):
class IdealizedStructure(MSection):
m_def = Section(
a_flask=dict(skip_none=True),
a_elastic=dict(type=InnerDoc),
description="""
Contains structural information for an idealized representation of the
material used in the calculation. This idealization is used for
......@@ -138,12 +135,13 @@ class IdealizedStructure(MSection):
original simulation cell.
"""
)
wyckoff_sets = SubSection(sub_section=WyckoffSet.m_def, repeats=True)
class Bulk(MSection):
m_def = Section(
a_flask=dict(skip_none=True),
a_elastic=dict(type=InnerDoc),
a_elastic="bulk",
description="""
Contains information that is specific to bulk crystalline materials.
"""
......@@ -172,13 +170,15 @@ class Bulk(MSection):
I = Body centered
R = Rhombohedral centring
F = All faces centred
"""
""",
a_search=Search()
)
crystal_system = Quantity(
type=MEnum("triclinic", "monoclinic", "orthorhombic", "tetragonal", "trigonal", "hexagonal", "cubic"),
description="""
The detected crystal system. One of seven possibilities in three dimensions.
"""
""",
a_search=Search()
)
has_free_wyckoff_parameters = Quantity(
type=bool,
......@@ -194,47 +194,52 @@ class Bulk(MSection):
description="""
Point group in Hermann-Mauguin notation, part of crystal structure
classification. There are 32 point groups in three dimensional space.
"""
""",
a_search=Search()
)
space_group_number = Quantity(
type=int,
description="""
Integer representation of the space group, part of crystal structure
classification, part of material definition.
"""
""",
a_search=Search()
)
space_group_international_short_symbol = Quantity(
type=str,
description="""
International short symbol notation of the space group.
"""
""",
a_search=Search()
)
structure_prototype = Quantity(
type=str,
description="""
The prototypical material for this crystal structure.
"""
""",
a_search=Search()
)
structure_type = Quantity(
type=str,
description="""
Classification according to known structure type, considering the point
group of the crystal and the occupations with different atom types.
"""
""",
a_search=Search()
)
strukturbericht_designation = Quantity(
type=str,
description="""
Classification of the material according to the historically grown "strukturbericht".
"""
""",
a_search=Search()
)
wyckoff_sets = SubSection(sub_section=WyckoffSet.m_def, repeats=True)
class Material(MSection):
m_def = Section(
a_flask=dict(skip_none=True),
a_elastic=dict(type=InnerDoc),
a_elastic="material",
description="""
Contains an overview of the type of material that was detected in this
entry.
......@@ -244,20 +249,23 @@ class Material(MSection):
type=MEnum(bulk="bulk", two_d="2D", one_d="1D", unavailable="unavailable"),
description="""
"Broad structural classification for the material, e.g. bulk, 2D, 1D... ",
"""
""",
a_search=Search()
)
material_hash = Quantity(
material_id = Quantity(
type=str,
description="""
A fixed length, unique material identifier in the form of a hash
digest.
"""
""",
a_search=Search()
)
material_name = Quantity(
type=str,
description="""
Most meaningful name for a material.
"""
""",
a_search=Search()
)
material_classification = Quantity(
type=str,
......@@ -272,7 +280,8 @@ class Material(MSection):
Formula giving the composition and occurrences of the elements in the
Hill notation. For periodic materials the formula is calculated fom the
primitive unit cell.
"""
""",
a_search=Search()
)
formula_reduced = Quantity(
type=str,
......@@ -280,20 +289,21 @@ class Material(MSection):
Formula giving the composition and occurrences of the elements in the
Hill notation whre the number of occurences have been divided by the
greatest common divisor.
"""
""",
a_search=Search()
)
# The idealized structure for this material
idealized_structure = SubSection(sub_section=IdealizedStructure.m_def, repeats=False)
# Bulk-specific properties
bulk = SubSection(sub_section=Bulk.m_def, repeats=False)
# The idealized structure for this material
idealized_structure = SubSection(sub_section=IdealizedStructure.m_def, repeats=False)
class Method(MSection):
m_def = Section(
a_flask=dict(skip_none=True),
a_elastic=dict(type=InnerDoc),
a_elastic="method",
description="""
Contains an overview of the methodology that was detected in this
entry.
......@@ -385,7 +395,7 @@ class Method(MSection):
class Calculation(MSection):
m_def = Section(
a_flask=dict(skip_none=True),
a_elastic=dict(type=InnerDoc),
a_elastic="calculation",
description="""
Contains an overview of the type of calculation that was detected in
this entry.
......@@ -412,7 +422,7 @@ class Calculation(MSection):
class Properties(MSection):
m_def = Section(
a_flask=dict(skip_none=True),
a_elastic=dict(type=InnerDoc),
a_elastic="properties",
description="""
Contains derived physical properties that are specific to the NOMAD
Encyclopedia.
......@@ -475,10 +485,10 @@ class Properties(MSection):
)
class section_encyclopedia(MSection):
class EncyclopediaMetadata(MSection):
m_def = Section(
a_flask=dict(skip_none=True),
a_elastic=dict(type=InnerDoc),
a_search='encyclopedia',
description="""
Section which stores information for the NOMAD Encyclopedia.
"""
......
......@@ -15,8 +15,8 @@
from typing import Any
from nomad.normalizing.normalizer import Normalizer
from nomad.metainfo.encyclopedia import (
section_encyclopedia,
from nomad.datamodel.encyclopedia import (
EncyclopediaMetadata,
Material,
Method,
Properties,
......@@ -205,8 +205,8 @@ class EncyclopediaNormalizer(Normalizer):
"""The caller will automatically log if the normalizer succeeds or ends
up with an exception.
"""
sec_enc = self.backend.entry_archive.m_create(section_encyclopedia)
status_enums = section_encyclopedia.status.type
sec_enc = self.backend.entry_archive.section_metadata.m_create(EncyclopediaMetadata)
status_enums = EncyclopediaMetadata.status.type
# Do nothing if section_run is not present
if self.section_run is None:
......
......@@ -25,13 +25,13 @@ import numpy as np
from matid import SymmetryAnalyzer
import matid.geometry
from nomad.metainfo.encyclopedia import (
from nomad.datamodel.encyclopedia import (
Material,
Properties,
WyckoffSet,
Bulk,
WyckoffVariables,
IdealizedStructure,
WyckoffSet,
WyckoffVariables,
)
from nomad.normalizing.encyclopedia.context import Context
from nomad.parsing.legacy import Backend
......@@ -72,10 +72,10 @@ class MaterialNormalizer():
formula = atomutils.get_formula_string(names, counts_reduced)
material.formula_reduced = formula
def material_hash(self, material: Material, spg_number: int, wyckoff_sets: List[WyckoffSet]) -> None:
def material_id(self, material: Material, spg_number: int, wyckoff_sets: List[WyckoffSet]) -> None:
# Create and store hash based on SHA512
norm_hash_string = atomutils.get_symmetry_string(spg_number, wyckoff_sets)
material.material_hash = hash(norm_hash_string)
material.material_id = hash(norm_hash_string)
def number_of_atoms(self, ideal: IdealizedStructure, std_atoms: Atoms) -> None:
ideal.number_of_atoms = len(std_atoms)
......@@ -334,9 +334,9 @@ class MaterialBulkNormalizer(MaterialNormalizer):
strukturbericht = re.sub('[$_{}]', '', strukturbericht)
bulk.strukturbericht_designation = strukturbericht
def wyckoff_sets(self, bulk: Bulk, wyckoff_sets: Dict) -> None:
def wyckoff_sets(self, ideal: IdealizedStructure, wyckoff_sets: Dict) -> None:
for group in wyckoff_sets:
wset = bulk.m_create(WyckoffSet)
wset = ideal.m_create(WyckoffSet)
if group.x is not None or group.y is not None or group.z is not None:
variables = wset.m_create(WyckoffVariables)
if group.x is not None:
......@@ -352,7 +352,7 @@ class MaterialBulkNormalizer(MaterialNormalizer):
def normalize(self, context: Context) -> None:
# Fetch resources
sec_system = context.representative_system
sec_enc = self.backend.entry_archive.section_encyclopedia
sec_enc = self.backend.entry_archive.section_metadata.encyclopedia
material = sec_enc.material
properties = sec_enc.properties
sec_symmetry = sec_system["section_symmetry"][0]
......@@ -371,7 +371,7 @@ class MaterialBulkNormalizer(MaterialNormalizer):
bulk = material.m_create(Bulk)
ideal = material.m_create(IdealizedStructure)
self.mass_density(properties, repr_atoms)
self.material_hash(material, spg_number, wyckoff_sets)
self.material_id(material, spg_number, wyckoff_sets)
self.number_of_atoms(ideal, std_atoms)
self.atom_labels(ideal, std_atoms)
self.atom_positions(ideal, std_atoms)
......@@ -394,7 +394,7 @@ class MaterialBulkNormalizer(MaterialNormalizer):
self.structure_type(bulk, sec_system)
self.structure_prototype(bulk, sec_system)
self.strukturbericht_designation(bulk, sec_system)
self.wyckoff_sets(bulk, wyckoff_sets)
self.wyckoff_sets(ideal, wyckoff_sets)
class Material2DNormalizer(MaterialNormalizer):
......@@ -465,7 +465,7 @@ class Material2DNormalizer(MaterialNormalizer):
def normalize(self, context: Context) -> None:
# Fetch resources
sec_enc = self.backend.entry_archive.section_encyclopedia
sec_enc = self.backend.entry_archive.section_metadata.encyclopedia
material = sec_enc.material
repr_atoms = context.representative_system.m_cache["representative_atoms"] # Temporary value stored by SystemNormalizer
symmetry_analyzer = self.get_symmetry_analyzer(repr_atoms)
......@@ -481,7 +481,7 @@ class Material2DNormalizer(MaterialNormalizer):
# Fill metainfo
ideal = material.m_create(IdealizedStructure)
self.periodicity(ideal, std_atoms)
self.material_hash(material, spg_number, wyckoff_sets)
self.material_id(material, spg_number, wyckoff_sets)
self.number_of_atoms(ideal, std_atoms)
self.atom_labels(ideal, std_atoms)
self.atom_positions(ideal, std_atoms)
......@@ -495,7 +495,7 @@ class Material2DNormalizer(MaterialNormalizer):
class Material1DNormalizer(MaterialNormalizer):
"""Processes structure related metainfo for Encyclopedia 1D structures.
"""
def material_hash_1d(self, material: Material, prim_atoms: Atoms) -> None:
def material_id_1d(self, material: Material, prim_atoms: Atoms) -> None:
"""Hash to be used as identifier for a material. Different 1D
materials are defined by their Coulomb matrix eigenvalues and their
Hill formulas.
......@@ -507,7 +507,7 @@ class Material1DNormalizer(MaterialNormalizer):
id_strings.append(fingerprint)
hash_seed = ", ".join(id_strings)
hash_val = hash(hash_seed)
material.material_hash = hash_val
material.material_id = hash_val
def lattice_vectors(self, ideal: IdealizedStructure, std_atoms: Atoms) -> None:
cell_normalized = std_atoms.get_cell()
......@@ -680,7 +680,7 @@ class Material1DNormalizer(MaterialNormalizer):
def normalize(self, context: Context) -> None:
# Fetch resources
sec_system = context.representative_system
sec_enc = self.backend.entry_archive.section_encyclopedia
sec_enc = self.backend.entry_archive.section_metadata.encyclopedia
material = sec_enc.material
repr_atoms = sec_system.m_cache["representative_atoms"] # Temporary value stored by SystemNormalizer
symmetry_analyzer = self.get_symmetry_analyzer(repr_atoms)
......@@ -701,5 +701,5 @@ class Material1DNormalizer(MaterialNormalizer):
self.lattice_vectors(ideal, std_atoms)
self.formula(material, names, counts)
self.formula_reduced(material, names, reduced_counts)
self.material_hash_1d(material, std_atoms)
self.material_id_1d(material, std_atoms)
self.lattice_parameters(ideal, std_atoms, ideal.periodicity)
......@@ -18,7 +18,7 @@ from collections import OrderedDict
import numpy as np
from pint import UnitRegistry
from nomad.metainfo.encyclopedia import (
from nomad.datamodel.encyclopedia import (
Material,
Method,
)
......@@ -384,7 +384,7 @@ class MethodDFTNormalizer(MethodNormalizer):
# Fetch resources
repr_method = context.representative_method
repr_system = context.representative_system
sec_enc = self.backend.entry_archive.section_encyclopedia
sec_enc = self.backend.entry_archive.section_metadata.encyclopedia
method = sec_enc.method
material = sec_enc.material
settings_basis_set = get_basis_set(context, self.backend, self.logger)
......@@ -423,7 +423,7 @@ class MethodGWNormalizer(MethodDFTNormalizer):
def normalize(self, context: Context) -> None:
# Fetch resources
repr_method = context.representative_method
sec_enc = self.backend.entry_archive.section_encyclopedia
sec_enc = self.backend.entry_archive.section_metadata.encyclopedia
method = sec_enc.method
# Fill metainfo
......
......@@ -14,7 +14,7 @@
import json
from nomad.metainfo.encyclopedia import (
from nomad.datamodel.encyclopedia import (
Calculation,
Properties,
)
......@@ -212,7 +212,7 @@ class PropertiesNormalizer():
return
# Fetch resources
sec_enc = self.backend.entry_archive.section_encyclopedia
sec_enc = self.backend.entry_archive.section_metadata.encyclopedia
properties = sec_enc.properties
calc_type = context.calc_type
material_type = context.material_type
......
......@@ -44,7 +44,7 @@ ureg = UnitRegistry()
def test_geometry_optimization(geometry_optimization: EntryArchive):
"""Tests that geometry optimizations are correctly processed."
"""
enc = geometry_optimization.entry_archive.section_encyclopedia
enc = geometry_optimization.entry_archive.section_metadata.encyclopedia
calc_type = enc.calculation.calculation_type
assert calc_type == "geometry optimization"
......@@ -52,7 +52,7 @@ def test_geometry_optimization(geometry_optimization: EntryArchive):
def test_molecular_dynamics(molecular_dynamics: EntryArchive):
"""Tests that geometry optimizations are correctly processed."
"""
enc = molecular_dynamics.entry_archive.section_encyclopedia
enc = molecular_dynamics.entry_archive.section_metadata.encyclopedia
calc_type = enc.calculation.calculation_type
assert calc_type == "molecular dynamics"
......@@ -60,7 +60,7 @@ def test_molecular_dynamics(molecular_dynamics: EntryArchive):
def test_1d_metainfo(one_d: EntryArchive):
"""Tests that metainfo for 1D systems is correctly processed.
"""
enc = one_d.entry_archive.section_encyclopedia
enc = one_d.entry_archive.section_metadata.encyclopedia
# Material
material = enc.material
assert material.material_type == "1D"
......@@ -80,7 +80,7 @@ def test_1d_metainfo(one_d: EntryArchive):
def test_2d_metainfo(two_d: EntryArchive):
"""Tests that metainfo for 2D systems is correctly processed.
"""
enc = two_d.entry_archive.section_encyclopedia
enc = two_d.entry_archive.section_metadata.encyclopedia
# Material
material = enc.material
assert material.material_type == "2D"
......@@ -101,7 +101,7 @@ def test_2d_metainfo(two_d: EntryArchive):
def test_bulk_metainfo(bulk: EntryArchive):
"""Tests that metainfo for bulk systems is correctly processed.
"""
enc = bulk.entry_archive.section_encyclopedia
enc = bulk.entry_archive.section_metadata.encyclopedia
# Material
material = enc.material
assert material.material_type == "bulk"
......@@ -115,7 +115,6 @@ def test_bulk_metainfo(bulk: EntryArchive):
assert bulk.bravais_lattice == "cF"
assert bulk.has_free_wyckoff_parameters is False
assert bulk.point_group == "m-3m"
assert bulk.wyckoff_sets is not None
assert bulk.space_group_number == 227
assert bulk.structure_type == "diamond"
assert bulk.structure_prototype == "C"
......@@ -124,6 +123,7 @@ def test_bulk_metainfo(bulk: EntryArchive):
# Idealized structure
ideal = enc.material.idealized_structure
assert ideal.wyckoff_sets is not None
assert ideal.number_of_atoms == 8
assert ideal.atom_labels == ["Si", "Si", "Si", "Si", "Si", "Si", "Si", "Si"]
assert ideal.atom_positions is not None
......@@ -142,21 +142,21 @@ def test_bulk_metainfo(bulk: EntryArchive):
def test_1d_material_identification():
# Original nanotube
nanotube1 = ase.build.nanotube(4, 4, vacuum=4)
enc = run_normalize_for_structure(nanotube1).entry_archive.section_encyclopedia
hash1 = enc.material.material_hash
enc = run_normalize_for_structure(nanotube1).entry_archive.section_metadata.encyclopedia
hash1 = enc.material.material_id
# Rotated copy
nanotube2 = nanotube1.copy()
nanotube2.rotate(90, "z", rotate_cell=True)
enc = run_normalize_for_structure(nanotube2).entry_archive.section_encyclopedia
hash2 = enc.material.material_hash
enc = run_normalize_for_structure(nanotube2).entry_archive.section_metadata.encyclopedia
hash2 = enc.material.material_id
assert hash2 == hash1
# Longer copy
nanotube3 = nanotube1.copy()
nanotube3 *= [1, 1, 2]
enc = run_normalize_for_structure(nanotube3).entry_archive.section_encyclopedia
hash3 = enc.material.material_hash
enc = run_normalize_for_structure(nanotube3).entry_archive.section_metadata.encyclopedia
hash3 = enc.material.material_id
assert hash3 == hash1
# Slightly distorted copies should match
......@@ -166,8 +166,8 @@ def test_1d_material_identification():
pos = nanotube4.get_positions()