Commit 30136a3b authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

A new iteration of metainfo re-implementation.

parent 9f4afd3c
"""
Some playground to try the CONCEPT.md ideas.
"""
# 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.
from typing import Dict, List, Any, Union, Type
import json
import ase.data
from typing import Type, TypeVar, Union, Tuple, Iterable, List, Any, Dict
import sys
class Units():
__module__ = sys.modules[__name__]
MObjectBound = TypeVar('MObjectBound', bound='MObject')
def __getattribute__(self, name):
return name
# Reflection
units = Units()
class MObjectMeta(type):
def __new__(self, cls_name, bases, dct):
cls = super().__new__(self, cls_name, bases, dct)
init = getattr(cls, '__init_section_cls__')
if init is not None:
init()
return cls
class Definition():
m_definition: Any = None
pass
Content = Tuple[MObjectBound, Union[List[MObjectBound], MObjectBound], str, MObjectBound]
class Property(Definition):
pass
class MObject(metaclass=MObjectMeta):
class Quantity(Property):
def __init__(
self,
name: str = None,
description: str = None,
parent_section: 'Section' = None,
shape: List[Union[str, int]] = [],
type: Union['Enum', type] = None,
unit: str = None,
derived: bool = False,
repeats: bool = False,
synonym: str = None):
def __init__(self, **kwargs):
self.m_section: 'Section' = kwargs.pop('m_section', getattr(self.__class__, 'm_section', None)) # TODO test with failure instead of None
self.m_data = dict(**kwargs)
self.name = name
self.parent_section = parent_section.m_definition if parent_section is not None else None
self.derived = derived
self.synonym = synonym
@classmethod
def __init_section_cls__(cls):
if not hasattr(__module__, 'Quantity') or not hasattr(__module__, 'Section'):
# no initialization during bootstrapping, will be done maunally
return
def __get__(self, obj: 'MetainfoObject', type: Type):
if obj is None:
# access on cls not obj
return self
m_section = getattr(cls, 'm_section', None)
if m_section is None:
m_section = Section()
setattr(cls, 'm_section', m_section)
m_section.name = cls.__name__
if self.derived:
derive_method = getattr(obj, 'm_derive_%s' % self.name, None)
for name, value in cls.__dict__.items():
if isinstance(value, Quantity):
value.name = name
m_section.m_add('Quantity', value)
if derive_method is None:
raise KeyError('Derived quantity %s is not implemented' % self.name)
def m_create(self, section: Union['Section', Type[MObjectBound]], **kwargs) -> MObjectBound:
"""Creates a subsection and adds it this this section
else:
return derive_method()
Args:
section: The section definition of the subsection. It is either the
definition itself, or the python class representing the section definition.
**kwargs: Are used to initialize the subsection.
elif self.synonym is not None:
return getattr(obj, self.synonym)
Returns:
The created subsection
Raises:
ValueError: If the given section is not a subsection of this section, or
this given definition is not a section at all.
"""
section_def: 'Section' = None
if isinstance(section, type) and hasattr(section, 'm_section'):
section_def = section.m_section
elif isinstance(section, Section):
section_def = section
else:
return obj.m_data.get(self.name, None)
raise ValueError('Not a section definition')
def __set__(self, obj: 'MetainfoObject', value: Any):
obj.m_data[self.name] = value
if section_def.parent != self.m_section:
raise ValueError('Not a subsection')
def __delete__(self, obj: 'MetainfoObject'):
del obj.m_data[self.name]
section_cls = section_def.section_cls
section_instance = section_cls(**kwargs)
def __repr__(self):
base = self.name
if self.parent_section is not None:
return '%s.%s' % (str(self.parent_section), base)
if section_def.repeats:
self.m_data.setdefault(section_def.name, []).append(section_instance)
else:
return base
self.m_data[section_def.name] = section_instance
return section_instance
class Section(Definition):
def __init__(
self,
name: str = None,
parent_section=None,
repeats: bool = False,
extends=None,
adds_to=None):
def m_set(self, name: str, value: Any):
self.m_data[name] = value
self.name = name
self.parent_section = parent_section.m_definition if parent_section is not None else None
self.repeats = repeats
def m_add(self, name: str, value: Any):
values = self.m_data.setdefault(name, [])
values.append(value)
def __get__(self, obj: 'MetainfoObject', type: Type):
return self
def m_get(self, name: str) -> Any:
try:
return self.m_data[name]
def __repr__(self):
base = self.name
if self.parent_section is not None:
return '%s.%s' % (str(self.parent_section), base)
else:
return base
except KeyError:
return self.m_section.attributes[name].default
def m_delete(self, name: str):
del self.m_data[name]
class Reference(Property):
pass
def m_to_dict(self):
pass
def m_to_json(self):
pass
class Enum:
def __init__(self, values: List[Any]):
self.values = values
def m_all_contents(self) -> Iterable[Content]:
for content in self.m_contents():
for sub_content in content[0].m_all_contents():
yield sub_content
yield content
class MetainfoObjectMeta(type):
def __new__(cls, cls_name, bases, dct):
cls.m_definition = dct.get('m_definition', None)
for name, value in dct.items():
if isinstance(value, Property):
value.name = name
value.parent_section = cls.m_definition
def m_contents(self) -> Iterable[Content]:
for name, attr in self.m_data.items():
if isinstance(attr, list):
for value in attr:
if isinstance(value, MObject):
yield value, attr, name, self
cls = super().__new__(cls, cls_name, bases, dct)
elif isinstance(attr, MObject):
yield value, value, name, self
if cls.m_definition is not None:
if cls.m_definition.name is None:
cls.m_definition.name = cls_name
def __repr__(self):
m_section_name = self.m_section.name
name = ''
if 'name' in self.m_data:
name = self.m_data['name']
return '%s:%s' % (name, m_section_name)
return cls
class Enum(list):
pass
class MetainfoObject(metaclass=MetainfoObjectMeta):
"""
Base class for all
"""
m_definition: Any = None
def __init__(self):
self.m_data = dict(m_defintion=self.m_definition.name)
# M3
def m_create(self, section_definition: Any, *args, **kwargs) -> Any:
"""
Creates a sub section of the given section definition.
"""
section_cls = section_definition
definition = section_definition.m_definition
sub_section = section_cls(*args, **kwargs)
if definition.repeats:
self.m_data.setdefault(definition.name, []).append(sub_section)
else:
# TODO test overwrite
self.m_data[definition.name] = sub_section
class Quantity(MObject):
m_section: 'Section' = None
name: 'Quantity' = None
type: 'Quantity' = None
shape: 'Quantity' = None
return sub_section
__name = property(lambda self: self.m_data['name'])
def m_get_definition(self, name):
"""
Returns the definition of the given quantity name.
"""
descriptor = getattr(type(self), name)
if descriptor is None:
raise KeyError
elif not isinstance(descriptor, Property):
raise KeyError
default = property(lambda self: None)
return descriptor
def __get__(self, obj, type=None):
return obj.m_get(self.__name)
def m_to_dict(self) -> Dict[str, Any]:
"""
Returns a JSON serializable dictionary version of this section (and all subsections).
"""
return {
key: value.m_to_dict() if isinstance(value, MetainfoObject) else value
for key, value in self.m_data.items()
}
def __set__(self, obj, value):
obj.m_set(self.__name, value)
def m_to_json(self) -> str:
return json.dumps(self.m_to_dict(), indent=2)
def __delete__(self, obj):
obj.m_delete(self.__name)
def m_validate(self) -> bool:
"""
Validates this sections content based on section and quantity definitions.
Can be overwritten to customize the validation.
"""
return True
def __repr__(self) -> str:
return self.m_to_json()
class Section(MObject):
m_section: 'Section' = None
name: 'Quantity' = None
repeats: 'Quantity' = None
parent: 'Quantity' = None
extends: 'Quantity' = None
__all_instances: List['Section'] = []
class Run(MetainfoObject):
m_definition = Section()
default = property(lambda self: [] if self.repeats else None)
section_cls = property(lambda self: self.__class__)
def __init__(self, **kwargs):
# The mechanism that produces default values, depends on parent. Without setting
# the parent default manually, an endless recursion will occur.
kwargs.setdefault('parent', None)
class System(MetainfoObject):
"""
The system is ...
"""
m_definition = Section(parent_section=Run, repeats=True)
super().__init__(**kwargs)
Section.__all_instances.append(self)
n_atoms = Quantity(type=int, derived=True)
# TODO cache
@property
def attributes(self) -> Dict[str, Union['Section', Quantity]]:
""" All attribute (sub section and quantity) definitions. """
atom_labels = Quantity(shape=['n_atoms'], type=Enum(ase.data.chemical_symbols))
"""
Atom labels are ...
"""
attributes: Dict[str, Union[Section, Quantity]] = dict(**self.quantities)
attributes.update(**self.sub_sections)
return attributes
atom_species = Quantity(shape=['n_atoms'], type=int, derived=True)
# TODO cache
@property
def quantities(self) -> Dict[str, Quantity]:
""" All quantity definition in the given section definition. """
atom_positions = Quantity(shape=['n_atoms', 3], type=float, unit=units.m)
return {
quantity.name: quantity
for quantity in self.m_data.get('Quantity', [])}
cell = Quantity(shape=[3, 3], type=float, unit=units.m)
lattice_vectors = Quantity(synonym='cell')
# TODO cache
@property
def sub_sections(self) -> Dict[str, 'Section']:
""" All sub section definitions for this section definition. """
pbc = Quantity(shape=[3], type=bool)
return {
sub_section.name: sub_section
for sub_section in Section.__all_instances
if sub_section.parent == self}
def m_derive_atom_species(self) -> List[int]:
return [ase.data.atomic_numbers[label] for label in self.atom_labels]
def m_derive_n_atoms(self) -> int:
return len(self.atom_labels)
Section.m_section = Section(repeats=True, name='Section')
Section.m_section.m_section = Section.m_section
Section.name = Quantity(type=str, name='name')
Section.repeats = Quantity(type=bool, name='repeats')
Section.parent = Quantity(type=Section.m_section, name='parent')
Section.extends = Quantity(type=Section.m_section, shape=['0..*'], name='extends')
class VaspSystem(MetainfoObject):
m_definition = Section(adds_to=System)
Quantity.m_section = Section(repeats=True, parent=Section.m_section, name='Quantity')
Quantity.name = Quantity(type=str, name='name')
Quantity.type = Quantity(type=Union[type, Enum, Section], name='type')
Quantity.shape = Quantity(type=Union[str, int], shape=['0..*'], name='shape')
vasp_specific = Quantity(type=str)
class Package(MObject):
m_section = Section()
name = Quantity(type=str)
run = Run()
print(run.m_definition)
system = run.m_create(System)
system.atom_labels = ['H', 'H', 'O']
system.atom_positions = [[0, 0, 0], [1, 0, 0], [0.5, 0.5, 0]]
system.cell = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
system.pbc = [False, False, False]
Section.m_section.parent = Package.m_section
print(system.atom_species)
print(system.lattice_vectors)
print(system.n_atoms)
print(system.__class__.m_definition)
print(system.m_definition)
print(system.m_get_definition('atom_labels'))
class Definition(MObject):
m_section = Section(extends=[Section.m_section, Quantity.m_section, Package.m_section])
print(run)
description = Quantity(type=str)
from typing import Dict, Any
class MetainfoObjectMeta(type):
def __new__(self, name, bases, dct):
cls = super().__new__(self, name, bases, dct)
for attr_name, attr in dct.items():
attr_cls = attr.__class__
if attr_cls.__module__ == __name__ and attr_cls.__name__ == 'Quantity':
attr.init(cls, attr_name)
if attr_cls.__module__ == __name__ and attr_cls.__name__ == 'Section':
attr.init(cls)
return cls
class MetainfoObject(metaclass=MetainfoObjectMeta):
def __init__(self):
self.m_data: Dict[str, Any] = {}
class BootstrapQuantity():
def __init__(self, **kwargs):
self.m_data = kwargs
class Quantity(MetainfoObject):
# m_definition = Section(repeats=True, parent_section=Quantity)
name = BootstrapQuantity(type=str)
type = BootstrapQuantity(type=type)
# section
def __init__(self, **kwargs):
super().__init__()
name = kwargs.pop('name', None)
if name is not None:
self.m_data.update(name=name)
for name, value in kwargs.items():
setattr(self, name, value)
def __get__(self, obj, type=None):
return obj.m_data.get(self.m_data['name'])
def __set__(self, obj, value):
obj.m_data[self.m_data['name']] = value
def init(self, cls, name):
self.name = name
for name, attr in Quantity.__dict__.items():
if isinstance(attr, BootstrapQuantity):
quantity = Quantity(name=name, **attr.m_data)
setattr(Quantity, name, quantity)
quantity.init(Quantity, name)
class Section(MetainfoObject):
# m_definition = Section(repeats=True, parent_section=Package)
name = Quantity(type=str)
repeats = Quantity(type=bool)
# parent_section = Quantity(type=Reference(Section))
# extends = Quantity(type=Reference(Section))
# adds_to = Quantity(type=Reference(Section))
def __init__(self, **kwargs):
super().__init__()
for name, value in kwargs.items():
setattr(self, name, value)
def __get__(self, obj, type=None):
return self
def init(self, cls):
self.name = cls.__name__
self.description = cls.__doc__.strip()
class System(MetainfoObject):
"""
This is the system documentation
"""
m_definition = Section(repeats=True)
atom_label = Quantity(type=str, shape=['n'])
system = System()
system.atom_label = ['H', 'H', 'O']
print(system.m_data)
print(system.m_definition.m_data)
print(System.m_definition.name)
"""
Some playground to try the CONCEPT.md ideas.
"""
from typing import Dict, List, Any, Union, Type
import json
import ase.data
class Units():
def __getattribute__(self, name):
return name
units = Units()
class Definition():
m_definition: Any = None
pass
class Property(Definition):
pass
class Quantity(Property):
def __init__(
self,
name: str = None,
description: str = None,
parent_section: 'Section' = None,
shape: List[Union[str, int]] = [],
type: Union['Enum', type] = None,
unit: str = None,
derived: bool = False,
repeats: bool = False,
synonym: str = None):
self.name = name
self.parent_section = parent_section.m_definition if parent_section is not None else None
self.derived = derived
self.synonym = synonym
def __get__(self, obj: 'MetainfoObject', type: Type):
if obj is None:
# access on cls not obj
return self
if self.derived:
derive_method = getattr(obj, 'm_derive_%s' % self.name, None)
if derive_method is None:
raise KeyError('Derived quantity %s is not implemented' % self.name)
else:
return derive_method()
elif self.synonym is not None:
return getattr(obj, self.synonym)
else:
return obj.m_data.get(self.name, None)
def __set__(self, obj: 'MetainfoObject', value: Any):
obj.m_data[self.name] = value
def __delete__(self, obj: 'MetainfoObject'):
del obj.m_data[self.name]