Commit d614e84e authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

New example metainfo package.

parent 7b431349
from .metainfo import MSection, Section, Quantity, Enum, units
from .metainfo import MSection, MCategory, Definition, Section, Quantity, Category, Package, Enum, units
""" An example metainfo package. """
import numpy as np
from nomad.metainfo import MSection, MCategory, Section, Quantity, Enum, Package, units
m_package = Package(links=['http://metainfo.nomad-coe.eu'])
class SystemHash(MCategory):
""" All quantities that contribute to what makes a system unique. """
class Run(MSection):
""" All data that belongs to a single code run. """
code_name = Quantity(type=str, description='The name of the code that was run.')
code_version = Quantity(type=str, description='The version of the code that was run.')
class VaspRun(MSection):
""" All VASP specific quantities for section Run. """
m_def = Section(extends=Run.m_def)
x_vasp_raw_format = Quantity(
type=Enum(['xml', 'outcar']),
description='The file format of the parsed VASP mainfile.')
class Parsing(MSection):
""" All data that describes the NOMAD parsing of this run. """
m_def = Section(parent=Run.m_def)
parser_name = Quantity(type=str)
parser_version = Quantity(type=str)
nomad_version = Quantity(type=str)
warnings = Quantity(type=str, shape=['0..*'])
class System(MSection):
""" All data that describes a simulated system. """
m_def = Section(repeats=True, parent=Run.m_def)
n_atoms = Quantity(
type=int, default=0,
description='Number of atoms in the simulated system.')
atom_labels = Quantity(
type=str, shape=['n_atoms'], categories=[SystemHash.m_def],
description='The atoms in the simulated systems.')
atom_positions = Quantity(
type=np.dtype('f'), shape=['n_atoms', 3], unit=units.m, categories=[SystemHash.m_def],
description='The atom positions in the simulated system.')
lattice_vectors = Quantity(
type=np.dtype('f'), shape=[3, 3], unit=units.m, categories=[SystemHash.m_def],
description='The lattice vectors of the simulated unit cell.')
periodic_dimensions = Quantity(
type=bool, shape=[3], categories=[SystemHash.m_def],
description='A vector of booleans indicating in which dimensions the unit cell is repeated.')
if __name__ == '__main__':
# Demonstration of how to reflect on the definitions
# All definitions are metainfo data themselves, and they can be accessed like any other
# metainfo data. E.g. all section definitions are sections themselves.
# To get quantities of a given section
print(Run.m_def.m_sub_sections(Quantity))
# Or all Sections in the package
print(m_package.m_sub_sections(Section)) # type: ignore, pylint: disable=undefined-variable
# There are also some definition specific helper methods.
# For example to get all attributes (Quantities and possible sub-sections) of a section.
print(Run.m_def.attributes)
# Demonstration on how to use the definitions, e.g. to create a run with system:
run = Run()
run.code_name = 'VASP'
run.code_version = '1.0.0'
system = run.m_create(System)
system.n_atoms = 3
system.atom_labels = ['H', 'H', 'O']
# Or to read data from existing metainfo data:
print(system.atom_labels)
# To serialize the data:
print(run.m_to_json(indent=2))
# print(m_package.m_to_json(indent=2)) # type: ignore, pylint: disable=undefined-variable
......@@ -137,13 +137,12 @@ See the reference of classes :class:`Section` and :class:`Quantities` for detail
"""
# TODO validation
# TODO serialization/deserialization
# TODO packages
from typing import Type, TypeVar, Union, Tuple, Iterable, List, Any, Dict, cast
import sys
import inspect
import re
import json
import numpy as np
from pint.unit import _Unit
......@@ -301,7 +300,7 @@ class MSection(metaclass=MObjectMeta):
# transfer name and description to m_def
m_def.name = cls.__name__
if cls.__doc__ is not None:
m_def.description = inspect.cleandoc(cls.__doc__)
m_def.description = inspect.cleandoc(cls.__doc__).strip()
m_def.section_cls = cls
# add sub_section to parent section
......@@ -314,7 +313,7 @@ class MSection(metaclass=MObjectMeta):
if isinstance(attr, Quantity):
attr.name = name
if attr.description is not None:
attr.description = inspect.cleandoc(attr.description)
attr.description = inspect.cleandoc(attr.description).strip()
attr.__doc__ = attr.description
# manual manipulation of m_data due to bootstrapping
m_def.m_data.setdefault('Quantity', []).append(attr)
......@@ -476,7 +475,8 @@ class MSection(metaclass=MObjectMeta):
return sub_section
def m_create(self, definition: SectionDef, **kwargs) -> 'MSection':
# TODO this should work with the section constructor
def m_create(self, definition: Type[MSectionBound], **kwargs) -> MSectionBound:
"""Creates a subsection and adds it this this section
Args:
......@@ -495,7 +495,7 @@ class MSection(metaclass=MObjectMeta):
section_cls = section_def.section_cls
section_instance = section_cls(m_def=section_def, m_parent=self, **kwargs)
return self.m_add_sub_section(section_instance)
return cast(MSectionBound, self.m_add_sub_section(section_instance))
def __resolve_quantity(self, definition: Union[str, 'Quantity']) -> 'Quantity':
"""Resolves and checks the given quantity definition. """
......@@ -568,11 +568,25 @@ class MSection(metaclass=MObjectMeta):
else:
yield name, self.m_data[name].m_to_dict()
for name in self.m_def.quantities:
for name, quantity in self.m_def.quantities.items():
if name in self.m_data:
value = getattr(self, name)
if hasattr(value, 'tolist'):
value = value.tolist()
# TODO
if isinstance(quantity.type, Section):
value = str(value)
# TODO
if isinstance(value, type):
value = str(value)
# TODO
if isinstance(value, np.dtype):
value = str(value)
# TODO
if isinstance(value, _Unit):
value = str(value)
yield name, value
return {key: value for key, value in items()}
......@@ -604,9 +618,9 @@ class MSection(metaclass=MObjectMeta):
section_instance.m_update(**dct)
return section_instance
def m_to_json(self):
def m_to_json(self, **kwargs):
"""Returns the data of this section as a json string. """
pass
return json.dumps(self.m_to_dict(), **kwargs)
def m_all_contents(self) -> Iterable[Content]:
"""Returns an iterable over all sub and sub subs sections. """
......@@ -651,7 +665,7 @@ class MCategory(metaclass=MObjectMeta):
# transfer name and description to m_def
m_def.name = cls.__name__
if cls.__doc__ is not None:
m_def.description = inspect.cleandoc(cls.__doc__)
m_def.description = inspect.cleandoc(cls.__doc__).strip()
# add section cls' section to the module's package
module_name = cls.__module__
......@@ -894,7 +908,7 @@ class Package(Definition):
pkg.name = module_name
if pkg.description is None and module.__doc__ is not None:
pkg.description = inspect.cleandoc(module.__doc__)
pkg.description = inspect.cleandoc(module.__doc__).strip()
return pkg
......
......@@ -15,7 +15,8 @@
import pytest
import numpy as np
from nomad.metainfo.metainfo import MSection, MCategory, Section, Quantity, Definition, Category, Package, sub_section
from nomad.metainfo.metainfo import MSection, MCategory, Section, Quantity, Definition, Category, sub_section
from nomad.metainfo.example import Run, System, SystemHash, Parsing, m_package as example_package
def assert_section_def(section_def: Section):
......@@ -79,37 +80,11 @@ class TestPureReflection:
assert getattr(obj, 'test_quantity') == 'test_value'
m_package = Package(description='package doc')
class MaterialDefining(MCategory):
"""Quantities that add to what constitutes a different material."""
pass
class Run(MSection):
""" This is the description.
And some more description.
"""
code_name = Quantity(
type=str, description='''
The code_name description.
''')
class System(MSection):
m_def = Section(repeats=True, parent=Run.m_def)
n_atoms = Quantity(type=int, default=0, categories=[MaterialDefining.m_def])
atom_label = Quantity(type=str, shape=['n_atoms'], categories=[MaterialDefining.m_def])
atom_positions = Quantity(type=np.dtype('f8'), shape=['n_atoms', 3])
class Parsing(MSection):
m_def = Section(parent=Run.m_def)
class TestM2:
""" Test for meta-info definitions. """
......@@ -125,7 +100,7 @@ class TestM2:
assert Run.m_def.parent is None
def test_quantities(self):
assert len(Run.m_def.quantities) == 1
assert len(Run.m_def.quantities) == 2
assert Run.m_def.quantities['code_name'] == Run.__dict__['code_name']
def test_sub_sections(self):
......@@ -133,7 +108,7 @@ class TestM2:
assert Run.m_def.sub_sections['System'] == System.m_def
def test_attributes(self):
assert len(Run.m_def.attributes) == 3
assert len(Run.m_def.attributes) == 4
assert Run.m_def.attributes['System'] == System.m_def
assert Run.m_def.attributes['code_name'] == Run.__dict__['code_name']
......@@ -162,19 +137,19 @@ class TestM2:
def test_quantity_description(self):
assert Run.code_name.description is not None
assert Run.code_name.description == 'The code_name description.'
assert Run.code_name.description == 'The name of the code that was run.'
assert Run.code_name.description.strip() == Run.code_name.description.strip()
def test_direct_category(self):
assert len(System.atom_label.categories)
assert MaterialDefining.m_def in System.atom_label.categories
assert System.atom_label in MaterialDefining.m_def.definitions
assert len(System.atom_labels.categories) == 1
assert SystemHash.m_def in System.atom_labels.categories
assert System.atom_labels in SystemHash.m_def.definitions
def test_package(self):
assert m_package.name == __name__
assert m_package.description is not None
assert len(m_package.m_sub_sections(Section)) == 3
assert len(m_package.m_sub_sections(Category)) == 1
assert example_package.name == 'nomad.metainfo.example'
assert example_package.description == 'An example metainfo package.'
assert len(example_package.m_sub_sections(Section)) == 4
assert len(example_package.m_sub_sections(Category)) == 1
class TestM1:
......@@ -206,7 +181,7 @@ class TestM1:
def test_defaults(self):
assert System().n_atoms == 0
assert System().atom_label is None
assert System().atom_labels is None
try:
System().does_not_exist
assert False, 'Supposed unreachable'
......@@ -268,7 +243,7 @@ class TestM1:
def test_wrong_shape_2(self):
try:
System().atom_label = 'label'
System().atom_labels = 'label'
assert False, 'Supposed unreachable'
except TypeError:
pass
......@@ -286,7 +261,7 @@ class TestM1:
run.code_name = 'test code name'
system: System = run.m_create(System)
system.n_atoms = 3
system.atom_label = ['H', 'H', 'O']
system.atom_labels = ['H', 'H', 'O']
system.atom_positions = np.array([[1.2e-10, 0, 0], [0, 1.2e-10, 0], [0, 0, 1.2e-10]])
return run
......@@ -299,7 +274,7 @@ class TestM1:
assert_section_instance(system)
assert system.m_def == System.m_def
assert system.n_atoms == 3
assert system.atom_label == ['H', 'H', 'O']
assert system.atom_labels == ['H', 'H', 'O']
assert type(system.atom_positions) == np.ndarray
def test_to_dict(self, example_data):
......
Supports Markdown
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