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

Continued to detail the metainfo implementation.

parent 2ace97ee
...@@ -36,110 +36,117 @@ class StructureEntry(MObject): ...@@ -36,110 +36,117 @@ class StructureEntry(MObject):
type=Enum(chemical_symbols), shape=['1..*'], type=Enum(chemical_symbols), shape=['1..*'],
links=optimade_links('h.6.2.1'), links=optimade_links('h.6.2.1'),
a_elastic=dict(type=Keyword), a_elastic=dict(type=Keyword),
a_optimade=Optimade(query=True, entry=True)) a_optimade=Optimade(query=True, entry=True),
""" Names of the different elements present in the structure. """ description='''
Names of the different elements present in the structure.
''')
nelements = Quantity( nelements = Quantity(
type=int, type=int,
links=optimade_links('h.6.2.2'), links=optimade_links('h.6.2.2'),
a_elastic=dict(type=Integer), a_elastic=dict(type=Integer),
a_optimade=Optimade(query=True, entry=True)) a_optimade=Optimade(query=True, entry=True),
""" Number of different elements in the structure as an integer. """ description='''
Number of different elements in the structure as an integer.
''')
elements_ratios = Quantity( elements_ratios = Quantity(
type=float, shape=['nelements'], type=float, shape=['nelements'],
links=optimade_links('h.6.2.3'), links=optimade_links('h.6.2.3'),
a_elastic=dict(type=lambda: Nested(ElementRatio), mapping=ElementRatio.from_structure_entry), a_elastic=dict(type=lambda: Nested(ElementRatio), mapping=ElementRatio.from_structure_entry),
a_optimade=Optimade(query=True, entry=True)) a_optimade=Optimade(query=True, entry=True),
""" Relative proportions of different elements in the structure. """ description='''
Relative proportions of different elements in the structure.
''')
chemical_formula_descriptive = Quantity( chemical_formula_descriptive = Quantity(
type=str, type=str,
links=optimade_links('h.6.2.4'), links=optimade_links('h.6.2.4'),
a_elastic=dict(type=Text, other_types=dict(keyword=Keyword)), a_elastic=dict(type=Text, other_types=dict(keyword=Keyword)),
a_optimade=Optimade(query=True, entry=True)) a_optimade=Optimade(query=True, entry=True),
""" description='''
The chemical formula for a structure as a string in a form chosen by the API The chemical formula for a structure as a string in a form chosen by the API
implementation. implementation.
""" ''')
chemical_formula_reduced = Quantity( chemical_formula_reduced = Quantity(
type=str, type=str,
links=optimade_links('h.6.2.5'), links=optimade_links('h.6.2.5'),
a_elastic=dict(type=Text, other_types=dict(keyword=Keyword)), a_elastic=dict(type=Text, other_types=dict(keyword=Keyword)),
a_optimade=Optimade(query=True, entry=True)) a_optimade=Optimade(query=True, entry=True),
""" description='''
The reduced chemical formula for a structure as a string with element symbols and The reduced chemical formula for a structure as a string with element symbols and
integer chemical proportion numbers. The proportion number MUST be omitted if it is 1. integer chemical proportion numbers. The proportion number MUST be omitted if it is 1.
""" ''')
chemical_formula_hill = Quantity( chemical_formula_hill = Quantity(
type=str, type=str,
links=optimade_links('h.6.2.6'), links=optimade_links('h.6.2.6'),
a_elastic=dict(type=Text, other_types=dict(keyword=Keyword)), a_elastic=dict(type=Text, other_types=dict(keyword=Keyword)),
a_optimade=Optimade(query=True, entry=False)) a_optimade=Optimade(query=True, entry=False),
""" description='''
The chemical formula for a structure in Hill form with element symbols followed by The chemical formula for a structure in Hill form with element symbols followed by
integer chemical proportion numbers. The proportion number MUST be omitted if it is 1. integer chemical proportion numbers. The proportion number MUST be omitted if it is 1.
""" ''')
chemical_formula_anonymous = Quantity( chemical_formula_anonymous = Quantity(
type=str, type=str,
links=optimade_links('h.6.2.7'), links=optimade_links('h.6.2.7'),
a_elastic=dict(type=Text, other_types=dict(keyword=Keyword)), a_elastic=dict(type=Text, other_types=dict(keyword=Keyword)),
a_optimade=Optimade(query=True, entry=True)) a_optimade=Optimade(query=True, entry=True),
""" description='''
The anonymous formula is the chemical_formula_reduced, but where the elements are The anonymous formula is the chemical_formula_reduced, but where the elements are
instead first ordered by their chemical proportion number, and then, in order left to instead first ordered by their chemical proportion number, and then, in order left to
right, replaced by anonymous symbols A, B, C, ..., Z, Aa, Ba, ..., Za, Ab, Bb, ... and right, replaced by anonymous symbols A, B, C, ..., Z, Aa, Ba, ..., Za, Ab, Bb, ... and
so on. so on.
""" ''')
dimension_types = Quantity( dimension_types = Quantity(
type=int, shape=[3], type=int, shape=[3],
links=optimade_links('h.6.2.8'), links=optimade_links('h.6.2.8'),
a_elastic=dict(type=Integer, mapping=lambda a: sum(a.dimension_types)), a_elastic=dict(type=Integer, mapping=lambda a: sum(a.dimension_types)),
a_optimade=Optimade(query=True, entry=True)) a_optimade=Optimade(query=True, entry=True),
""" description='''
List of three integers. For each of the three directions indicated by the three lattice List of three integers. For each of the three directions indicated by the three lattice
vectors (see property lattice_vectors). This list indicates if the direction is vectors (see property lattice_vectors). This list indicates if the direction is
periodic (value 1) or non-periodic (value 0). Note: the elements in this list each periodic (value 1) or non-periodic (value 0). Note: the elements in this list each
refer to the direction of the corresponding entry in lattice_vectors and not refer to the direction of the corresponding entry in lattice_vectors and not
the Cartesian x, y, z directions. the Cartesian x, y, z directions.
""" ''')
lattice_vectors = Quantity( lattice_vectors = Quantity(
type=float, shape=[3, 3], unit=units.angstrom, type=float, shape=[3, 3], unit=units.angstrom,
links=optimade_links('h.6.2.9'), links=optimade_links('h.6.2.9'),
a_optimade=Optimade(query=False, entry=True)) a_optimade=Optimade(query=False, entry=True),
""" The three lattice vectors in Cartesian coordinates, in ångström (Å). """ description='''
The three lattice vectors in Cartesian coordinates, in ångström (Å).
''')
cartesian_site_positions = Quantity( cartesian_site_positions = Quantity(
type=float, shape=['nsites', 3], unit=units.angstrom, type=float, shape=['nsites', 3], unit=units.angstrom,
links=optimade_links('h.6.2.10'), links=optimade_links('h.6.2.10'),
a_optimade=Optimade(query=False, entry=True)) a_optimade=Optimade(query=False, entry=True), description='''
""" Cartesian positions of each site. A site is an atom, a site potentially occupied by
Cartesian positions of each site. A site is an atom, a site potentially occupied by an atom, or a placeholder for a virtual mixture of atoms (e.g., in a virtual crystal
an atom, or a placeholder for a virtual mixture of atoms (e.g., in a virtual crystal approximation).
approximation). ''')
"""
nsites = Quantity( nsites = Quantity(
type=int, type=int,
links=optimade_links('h.6.2.11'), links=optimade_links('h.6.2.11'),
a_elastic=dict(type=Integer), a_elastic=dict(type=Integer),
a_optimade=Optimade(query=True, entry=True)) a_optimade=Optimade(query=True, entry=True), description='''
""" An integer specifying the length of the cartesian_site_positions property. """ An integer specifying the length of the cartesian_site_positions property.
''')
species_at_sites = Quantity( species_at_sites = Quantity(
type=str, shape=['nsites'], type=str, shape=['nsites'],
links=optimade_links('h.6.2.12'), links=optimade_links('h.6.2.12'),
a_optimade=Optimade(query=False, entry=True)) a_optimade=Optimade(query=False, entry=True), description='''
""" Name of the species at each site (where values for sites are specified with the same
Name of the species at each site (where values for sites are specified with the same order of the cartesian_site_positions property). The properties of the species are
order of the cartesian_site_positions property). The properties of the species are found in the species property.
found in the species property. ''')
"""
# TODO assemblies # TODO assemblies
...@@ -147,16 +154,15 @@ class StructureEntry(MObject): ...@@ -147,16 +154,15 @@ class StructureEntry(MObject):
type=Enum(['disorder', 'unknown_positions', 'assemblies']), shape=['1..*'], type=Enum(['disorder', 'unknown_positions', 'assemblies']), shape=['1..*'],
links=optimade_links('h.6.2.15'), links=optimade_links('h.6.2.15'),
a_elastic=dict(type=Keyword), a_elastic=dict(type=Keyword),
a_optimade=Optimade(query=True, entry=True)) a_optimade=Optimade(query=True, entry=True), description='''
""" A list of strings that flag which special features are used by the structure.
A list of strings that flag which special features are used by the structure.
- disorder: This flag MUST be present if any one entry in the species list has a - disorder: This flag MUST be present if any one entry in the species list has a
chemical_symbols list that is longer than 1 element. chemical_symbols list that is longer than 1 element.
- unknown_positions: This flag MUST be present if at least one component of the - unknown_positions: This flag MUST be present if at least one component of the
cartesian_site_positions list of lists has value null. cartesian_site_positions list of lists has value null.
- assemblies: This flag MUST be present if the assemblies list is present. - assemblies: This flag MUST be present if the assemblies list is present.
""" ''')
class Species(MObject): class Species(MObject):
...@@ -171,59 +177,56 @@ class Species(MObject): ...@@ -171,59 +177,56 @@ class Species(MObject):
links=optimade_links('h.6.2.13')) links=optimade_links('h.6.2.13'))
name = Quantity( name = Quantity(
type=str, type=str, a_optimade=Optimade(entry=True), description='''
a_optimade=Optimade(entry=True)) The name of the species; the name value MUST be unique in the species list.
""" The name of the species; the name value MUST be unique in the species list. """ ''')
chemical_symbols = Quantity( chemical_symbols = Quantity(
type=Enum(chemical_symbols + ['x', 'vacancy']), shape=['1..*'], type=Enum(chemical_symbols + ['x', 'vacancy']), shape=['1..*'],
a_optimade=Optimade(entry=True)) a_optimade=Optimade(entry=True), description='''
""" A list of strings of all chemical elements composing this species.
A list of strings of all chemical elements composing this species.
It MUST be one of the following: It MUST be one of the following:
- a valid chemical-element name, or - a valid chemical-element name, or
- the special value "X" to represent a non-chemical element, or - the special value "X" to represent a non-chemical element, or
- the special value "vacancy" to represent that this site has a non-zero probability - the special value "vacancy" to represent that this site has a non-zero probability
of having a vacancy (the respective probability is indicated in the concentration of having a vacancy (the respective probability is indicated in the concentration
list, see below). list, see below).
If any one entry in the species list has a chemical_symbols list that is longer than 1 If any one entry in the species list has a chemical_symbols list that is longer than 1
element, the correct flag MUST be set in the list structure_features (see element, the correct flag MUST be set in the list structure_features (see
structure_features) structure_features)
""" ''')
concentration = Quantity( concentration = Quantity(
type=float, shape=['1..*'], type=float, shape=['1..*'],
a_optimade=Optimade(entry=True)) a_optimade=Optimade(entry=True), description='''
""" A list of floats, with same length as chemical_symbols. The numbers represent the
A list of floats, with same length as chemical_symbols. The numbers represent the relative concentration of the corresponding chemical symbol in this species. The
relative concentration of the corresponding chemical symbol in this species. The numbers SHOULD sum to one. Cases in which the numbers do not sum to one typically fall
numbers SHOULD sum to one. Cases in which the numbers do not sum to one typically fall only in the following two categories:
only in the following two categories:
- Numerical errors when representing float numbers in fixed precision, e.g. for two
- Numerical errors when representing float numbers in fixed precision, e.g. for two chemical symbols with concentrations 1/3 and 2/3, the concentration might look
chemical symbols with concentrations 1/3 and 2/3, the concentration might look something like [0.33333333333, 0.66666666666]. If the client is aware that the sum
something like [0.33333333333, 0.66666666666]. If the client is aware that the sum is not one because of numerical precision, it can renormalize the values so that the
is not one because of numerical precision, it can renormalize the values so that the sum is exactly one.
sum is exactly one. - Experimental errors in the data present in the database. In this case, it is the
- Experimental errors in the data present in the database. In this case, it is the responsibility of the client to decide how to process the data.
responsibility of the client to decide how to process the data.
Note that concentrations are uncorrelated between different sites (even of the same
Note that concentrations are uncorrelated between different sites (even of the same species).
species). ''')
"""
mass = Quantity(type=float, unit=units.amu, a_optimade=dict(entry='optional')) mass = Quantity(type=float, unit=units.amu, a_optimade=dict(entry='optional'))
original_name = Quantity(type=str, a_optimade=dict(entry='optional')) original_name = Quantity(type=str, a_optimade=dict(entry='optional'), description='''
""" Can be any valid Unicode string, and SHOULD contain (if specified) the name of the
Can be any valid Unicode string, and SHOULD contain (if specified) the name of the species that is used internally in the source database.
species that is used internally in the source database.
Note: With regards to "source database", we refer to the immediate source being Note: With regards to "source database", we refer to the immediate source being
queried via the OPTiMaDe API implementation. The main use of this field is for source queried via the OPTiMaDe API implementation. The main use of this field is for source
databases that use species names, containing characters that are not allowed (see databases that use species names, containing characters that are not allowed (see
description of the species_at_sites list). description of the species_at_sites list).
""" ''')
This diff is collapsed.
...@@ -48,6 +48,7 @@ pyyaml ...@@ -48,6 +48,7 @@ pyyaml
tabulate tabulate
cachetools cachetools
zipfile37 zipfile37
inflection
# dev/ops related # dev/ops related
setuptools setuptools
......
...@@ -12,7 +12,31 @@ ...@@ -12,7 +12,31 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from nomad.metainfo.metainfo import MObject, Section, Quantity from nomad.metainfo.metainfo import MObject, Section, Quantity, Definition, sub_section
def assert_section_def(section_def: Section):
assert isinstance(section_def, Section)
assert section_def.m_section is not None
assert isinstance(section_def.m_section, Section)
assert section_def.m_section.name is not None
assert section_def.m_section.m_section == Section.m_section
assert section_def.name is not None
if section_def.parent is not None:
if section_def.parent != section_def:
assert_section_def(section_def.parent)
if section_def.repeats:
assert section_def.parent is not None
def assert_section_instance(section: MObject):
assert_section_def(section.m_section)
if section.m_parent is not None:
assert section.m_parent.m_sub_section(section.m_section, section.m_parent_index) == section
class TestM3: class TestM3:
...@@ -21,12 +45,22 @@ class TestM3: ...@@ -21,12 +45,22 @@ class TestM3:
def test_section(self): def test_section(self):
assert Section.m_section == Section.m_section.m_section assert Section.m_section == Section.m_section.m_section
assert Section.m_section.name == 'Section' assert Section.m_section.name == 'Section'
assert Section.name is not None
assert Section.name == Definition.name
assert Section.name.m_section == Quantity.m_section
assert Section.description.description is not None
assert Section.m_section.m_sub_section(Quantity, 0).name in Section.m_section.attributes
assert_section_instance(Section.m_section)
def test_quantity(self): def test_quantity(self):
assert Quantity.m_section.m_section == Section.m_section assert Quantity.m_section.m_section == Section.m_section
assert Quantity.m_section.name == 'Quantity' assert Quantity.m_section.name == 'Quantity'
assert Quantity.m_section.parent == Section.m_section assert Quantity.m_section.parent == Section.m_section
assert_section_instance(Quantity.m_section)
class TestPureReflection: class TestPureReflection:
""" Test for using meta-info instances without knowing/using the respective definitions. """ """ Test for using meta-info instances without knowing/using the respective definitions. """
...@@ -48,8 +82,10 @@ class Run(MObject): ...@@ -48,8 +82,10 @@ class Run(MObject):
And some more description. And some more description.
""" """
code_name = Quantity(type=str) code_name = Quantity(
""" The code_name description. """ type=str, description='''
The code_name description.
''')
class System(MObject): class System(MObject):
...@@ -65,6 +101,10 @@ class Parsing(MObject): ...@@ -65,6 +101,10 @@ class Parsing(MObject):
class TestM2: class TestM2:
""" Test for meta-info definitions. """ """ Test for meta-info definitions. """
def test_basics(self):
assert_section_def(Run.m_section)
assert_section_def(System.m_section)
def test_default_section_def(self): def test_default_section_def(self):
""" A section class without an explicit section def must set a default section def. """ """ A section class without an explicit section def must set a default section def. """
assert Run.m_section is not None assert Run.m_section is not None
...@@ -110,6 +150,7 @@ class TestM2: ...@@ -110,6 +150,7 @@ class TestM2:
def test_quantity_description(self): def test_quantity_description(self):
assert Run.code_name.description is not None assert Run.code_name.description is not None
assert Run.code_name.description == 'The code_name description.'
assert Run.code_name.description.strip() == Run.code_name.description.strip() assert Run.code_name.description.strip() == Run.code_name.description.strip()
...@@ -126,6 +167,8 @@ class TestM1: ...@@ -126,6 +167,8 @@ class TestM1:
assert run.m_section.name == 'Run' assert run.m_section.name == 'Run'
assert len(run.m_data) == 0 assert len(run.m_data) == 0
assert_section_instance(run)
def test_system(self): def test_system(self):
class System(MObject): class System(MObject):
m_section = Section() m_section = Section()
...@@ -136,6 +179,8 @@ class TestM1: ...@@ -136,6 +179,8 @@ class TestM1:
assert len(system.atom_labels) == 1 assert len(system.atom_labels) == 1
assert len(system.m_data) == 1 assert len(system.m_data) == 1
assert_section_instance(system)
def test_defaults(self): def test_defaults(self):
assert System().n_atoms == 0 assert System().n_atoms == 0
assert System().atom_label is None assert System().atom_label is None
...@@ -150,6 +195,22 @@ class TestM1: ...@@ -150,6 +195,22 @@ class TestM1:
def test_m_section(self): def test_m_section(self):
assert Run().m_section == Run.m_section assert Run().m_section == Run.m_section
def test_children_parent(self):
run = Run()
system = run.m_create(System)
assert run.system[0] == system # pylint: disable=E1101
assert run.m_sub_section(System, 0) == system
def test_children_sub_section(self):
setattr(Run, 'a_system_sub_section', sub_section(System))
run = Run()
system = run.m_create(System)
assert run.a_system_sub_section[0] == system # pylint: disable=E1101
assert run.system[0] == system # pylint: disable=E1101
assert run.m_sub_section(System, 0) == system
def test_parent_repeats(self): def test_parent_repeats(self):
run = Run() run = Run()
system = run.m_create(System) system = run.m_create(System)
......
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