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):
type=Enum(chemical_symbols), shape=['1..*'],
links=optimade_links('h.6.2.1'),
a_elastic=dict(type=Keyword),
a_optimade=Optimade(query=True, entry=True))
""" Names of the different elements present in the structure. """
a_optimade=Optimade(query=True, entry=True),
description='''
Names of the different elements present in the structure.
''')
nelements = Quantity(
type=int,
links=optimade_links('h.6.2.2'),
a_elastic=dict(type=Integer),
a_optimade=Optimade(query=True, entry=True))
""" Number of different elements in the structure as an integer. """
a_optimade=Optimade(query=True, entry=True),
description='''
Number of different elements in the structure as an integer.
''')
elements_ratios = Quantity(
type=float, shape=['nelements'],
links=optimade_links('h.6.2.3'),
a_elastic=dict(type=lambda: Nested(ElementRatio), mapping=ElementRatio.from_structure_entry),
a_optimade=Optimade(query=True, entry=True))
""" Relative proportions of different elements in the structure. """
a_optimade=Optimade(query=True, entry=True),
description='''
Relative proportions of different elements in the structure.
''')
chemical_formula_descriptive = Quantity(
type=str,
links=optimade_links('h.6.2.4'),
a_elastic=dict(type=Text, other_types=dict(keyword=Keyword)),
a_optimade=Optimade(query=True, entry=True))
"""
The chemical formula for a structure as a string in a form chosen by the API
implementation.
"""
a_optimade=Optimade(query=True, entry=True),
description='''
The chemical formula for a structure as a string in a form chosen by the API
implementation.
''')
chemical_formula_reduced = Quantity(
type=str,
links=optimade_links('h.6.2.5'),
a_elastic=dict(type=Text, other_types=dict(keyword=Keyword)),
a_optimade=Optimade(query=True, entry=True))
"""
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.
"""
a_optimade=Optimade(query=True, entry=True),
description='''
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.
''')
chemical_formula_hill = Quantity(
type=str,
links=optimade_links('h.6.2.6'),
a_elastic=dict(type=Text, other_types=dict(keyword=Keyword)),
a_optimade=Optimade(query=True, entry=False))
"""
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.
"""
a_optimade=Optimade(query=True, entry=False),
description='''
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.
''')
chemical_formula_anonymous = Quantity(
type=str,
links=optimade_links('h.6.2.7'),
a_elastic=dict(type=Text, other_types=dict(keyword=Keyword)),
a_optimade=Optimade(query=True, entry=True))
"""
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
right, replaced by anonymous symbols A, B, C, ..., Z, Aa, Ba, ..., Za, Ab, Bb, ... and
so on.
"""
a_optimade=Optimade(query=True, entry=True),
description='''
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
right, replaced by anonymous symbols A, B, C, ..., Z, Aa, Ba, ..., Za, Ab, Bb, ... and
so on.
''')
dimension_types = Quantity(
type=int, shape=[3],
links=optimade_links('h.6.2.8'),
a_elastic=dict(type=Integer, mapping=lambda a: sum(a.dimension_types)),
a_optimade=Optimade(query=True, entry=True))
"""
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
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
the Cartesian x, y, z directions.
"""
a_optimade=Optimade(query=True, entry=True),
description='''
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
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
the Cartesian x, y, z directions.
''')
lattice_vectors = Quantity(
type=float, shape=[3, 3], unit=units.angstrom,
links=optimade_links('h.6.2.9'),
a_optimade=Optimade(query=False, entry=True))
""" The three lattice vectors in Cartesian coordinates, in ångström (Å). """
a_optimade=Optimade(query=False, entry=True),
description='''
The three lattice vectors in Cartesian coordinates, in ångström (Å).
''')
cartesian_site_positions = Quantity(
type=float, shape=['nsites', 3], unit=units.angstrom,
links=optimade_links('h.6.2.10'),
a_optimade=Optimade(query=False, entry=True))
"""
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
approximation).
"""
a_optimade=Optimade(query=False, entry=True), description='''
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
approximation).
''')
nsites = Quantity(
type=int,
links=optimade_links('h.6.2.11'),
a_elastic=dict(type=Integer),
a_optimade=Optimade(query=True, entry=True))
""" An integer specifying the length of the cartesian_site_positions property. """
a_optimade=Optimade(query=True, entry=True), description='''
An integer specifying the length of the cartesian_site_positions property.
''')
species_at_sites = Quantity(
type=str, shape=['nsites'],
links=optimade_links('h.6.2.12'),
a_optimade=Optimade(query=False, entry=True))
"""
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
found in the species property.
"""
a_optimade=Optimade(query=False, entry=True), description='''
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
found in the species property.
''')
# TODO assemblies
......@@ -147,16 +154,15 @@ class StructureEntry(MObject):
type=Enum(['disorder', 'unknown_positions', 'assemblies']), shape=['1..*'],
links=optimade_links('h.6.2.15'),
a_elastic=dict(type=Keyword),
a_optimade=Optimade(query=True, entry=True))
"""
A list of strings that flag which special features are used by the structure.
a_optimade=Optimade(query=True, entry=True), description='''
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
chemical_symbols list that is longer than 1 element.
- unknown_positions: This flag MUST be present if at least one component of the
cartesian_site_positions list of lists has value null.
- assemblies: This flag MUST be present if the assemblies list is present.
"""
- 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.
- unknown_positions: This flag MUST be present if at least one component of the
cartesian_site_positions list of lists has value null.
- assemblies: This flag MUST be present if the assemblies list is present.
''')
class Species(MObject):
......@@ -171,59 +177,56 @@ class Species(MObject):
links=optimade_links('h.6.2.13'))
name = Quantity(
type=str,
a_optimade=Optimade(entry=True))
""" The name of the species; the name value MUST be unique in the species list. """
type=str, a_optimade=Optimade(entry=True), description='''
The name of the species; the name value MUST be unique in the species list.
''')
chemical_symbols = Quantity(
type=Enum(chemical_symbols + ['x', 'vacancy']), shape=['1..*'],
a_optimade=Optimade(entry=True))
"""
A list of strings of all chemical elements composing this species.
a_optimade=Optimade(entry=True), description='''
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
- 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
of having a vacancy (the respective probability is indicated in the concentration
list, see below).
- a valid chemical-element name, 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
of having a vacancy (the respective probability is indicated in the concentration
list, see below).
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
structure_features)
"""
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
structure_features)
''')
concentration = Quantity(
type=float, shape=['1..*'],
a_optimade=Optimade(entry=True))
"""
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
numbers SHOULD sum to one. Cases in which the numbers do not sum to one typically fall
only in the following two categories:
- 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
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
sum is exactly one.
- 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.
Note that concentrations are uncorrelated between different sites (even of the same
species).
"""
a_optimade=Optimade(entry=True), description='''
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
numbers SHOULD sum to one. Cases in which the numbers do not sum to one typically fall
only in the following two categories:
- 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
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
sum is exactly one.
- 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.
Note that concentrations are uncorrelated between different sites (even of the same
species).
''')
mass = Quantity(type=float, unit=units.amu, a_optimade=dict(entry='optional'))
original_name = Quantity(type=str, a_optimade=dict(entry='optional'))
"""
Can be any valid Unicode string, and SHOULD contain (if specified) the name of the
species that is used internally in the source database.
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
species that is used internally in the source database.
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
databases that use species names, containing characters that are not allowed (see
description of the species_at_sites list).
"""
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
databases that use species names, containing characters that are not allowed (see
description of the species_at_sites list).
''')
This diff is collapsed.
......@@ -48,6 +48,7 @@ pyyaml
tabulate
cachetools
zipfile37
inflection
# dev/ops related
setuptools
......
......@@ -12,7 +12,31 @@
# See the License for the specific language governing permissions and
# 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:
......@@ -21,12 +45,22 @@ class TestM3:
def test_section(self):
assert Section.m_section == Section.m_section.m_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):
assert Quantity.m_section.m_section == Section.m_section
assert Quantity.m_section.name == 'Quantity'
assert Quantity.m_section.parent == Section.m_section
assert_section_instance(Quantity.m_section)
class TestPureReflection:
""" Test for using meta-info instances without knowing/using the respective definitions. """
......@@ -48,8 +82,10 @@ class Run(MObject):
And some more description.
"""
code_name = Quantity(type=str)
""" The code_name description. """
code_name = Quantity(
type=str, description='''
The code_name description.
''')
class System(MObject):
......@@ -65,6 +101,10 @@ class Parsing(MObject):
class TestM2:
""" 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):
""" A section class without an explicit section def must set a default section def. """
assert Run.m_section is not None
......@@ -110,6 +150,7 @@ 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.strip() == Run.code_name.description.strip()
......@@ -126,6 +167,8 @@ class TestM1:
assert run.m_section.name == 'Run'
assert len(run.m_data) == 0
assert_section_instance(run)
def test_system(self):
class System(MObject):
m_section = Section()
......@@ -136,6 +179,8 @@ class TestM1:
assert len(system.atom_labels) == 1
assert len(system.m_data) == 1
assert_section_instance(system)
def test_defaults(self):
assert System().n_atoms == 0
assert System().atom_label is None
......@@ -150,6 +195,22 @@ class TestM1:
def test_m_section(self):
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):
run = Run()
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