Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

plugin migration. #8

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 74 additions & 39 deletions src/aiida_atomistic/data/structure/mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,43 @@
"magmoms": 1e-4, # _MAGMOM_THRESHOLD
}

class RedundantKind:
"""
A class to resemble the Kind class as we find in aiida-core.
This is done in order to help a lot the plugin migration, as structure.kinds
is used really often.
"""
def __init__(self, site_instance):
self.mass = site_instance.masses
self.symbol = site_instance.symbols
self.weights = site_instance.weights
self.name = site_instance.kinds
self.has_vacancies = site_instance.has_vacancies
self.is_alloy = site_instance.is_alloy


class GetterMixin(HubbardGetterMixin):

# Start redundant properties: This is mainly for make easier migrations
@property
def cell(self):
return self.properties.cell

@property
def pbc(self):
return self.properties.pbc

@property
def sites(self):
return self.properties.sites

@property
def kinds(self):
# This helps in plugin migration,
# a lot of them use kinds as defined in orm.StructureData
return [RedundantKind(site) for site in self.properties.sites]
# End redundant properties.

@property
def is_alloy(self):
return any(_.is_alloy for _ in self.properties.sites)
Expand Down Expand Up @@ -376,6 +411,15 @@ def get_cell_volume(self):
from aiida_atomistic.data.structure.utils import calc_cell_volume
return calc_cell_volume(self.properties.cell)

def get_symbols_set(self):
"""Return a set containing the names of all elements involved in
this structure (i.e., for it joins the list of symbols for each
kind k in the structure).

:returns: a set of strings of element names.
"""
return set(itertools.chain.from_iterable(site.kinds for site in self.sites))

def get_cif(self, converter="ase", store=False, **kwargs):
"""Creates :py:class:`aiida.orm.nodes.data.cif.CifData`.

Expand Down Expand Up @@ -452,7 +496,7 @@ def get_formula(self, mode="hill", separator=""):
used to group and/or order the symbols in the formula
"""
from aiida_atomistic.data.structure.utils import get_formula
symbol_list = [s.symbol for s in self.properties.sites]
symbol_list = [s.symbols for s in self.properties.sites]

return get_formula(symbol_list, mode=mode, separator=separator)

Expand Down Expand Up @@ -784,7 +828,7 @@ def _prepare_xsf(self, main_file_name=""):
# I checked above that it is not an alloy, therefore I take the
# first symbol
return_string += (
f"{_atomic_numbers[self.get_kind(site.kinds).symbols[0]]} "
f"{_atomic_numbers[site.symbols]} "
)
return_string += "%18.10f %18.10f %18.10f\n" % tuple(site.position)
return return_string.encode("utf-8"), {}
Expand All @@ -807,7 +851,7 @@ def _prepare_chemdoodle(self, main_file_name=""):

# Get cell vectors and atomic position
lattice_vectors = np.array(self.base.attributes.get("cell"))
base_sites = self.base.attributes.get("sites")
base_sites = self.sites

start1 = -int(supercell_factors[0] / 2)
start2 = -int(supercell_factors[1] / 2)
Expand Down Expand Up @@ -835,15 +879,15 @@ def _prepare_chemdoodle(self, main_file_name=""):
- center
).tolist()

kind_name = base_site["kinds"]
kind_string = self.get_kind(kind_name).get_symbols_string()
kind_name = base_site.kinds
kind_string = base_site.symbols

atoms_json.append(
{
"l": kind_string,
"x": base_site["positions"][0] + shift[0],
"y": base_site["positions"][1] + shift[1],
"z": base_site["positions"][2] + shift[2],
"x": np.array(base_site.positions[0]) + shift[0],
"y": np.array(base_site.positions[1]) + shift[1],
"z": np.array(base_site.positions[2]) + shift[2],
"atomic_elements_html": atom_kinds_to_html(kind_string),
}
)
Expand Down Expand Up @@ -899,7 +943,7 @@ def _prepare_xyz(self, main_file_name=""):
# first symbol
return_list.append(
"{:6s} {:18.10f} {:18.10f} {:18.10f}".format(
self.get_kind(site.kinds).symbols[0],
site.symbols,
site.position[0],
site.position[1],
site.position[2],
Expand All @@ -925,7 +969,7 @@ def _parse_xyz(self, inputstring):
self.properties.pbc = (False, False, False)

for sym, position in atoms:
self.add_atom(atom_info={'symbols':sym, 'position':position})
self.add_atom(atom_info={'symbols':sym, 'positions':position})

def _adjust_default_cell(
self, vacuum_factor=1.0, vacuum_addition=10.0, pbc=(False, False, False)
Expand All @@ -942,14 +986,14 @@ def get_extremas_from_positions(positions):
)

# Calculating the minimal cell:
positions = np.array([site.position for site in self.properties.sites])
positions = np.array([site.positions for site in self.properties.sites])
position_min, _ = get_extremas_from_positions(positions)

# Translate the structure to the origin, such that the minimal values in each dimension
# amount to (0,0,0)
positions -= position_min
for index, site in enumerate(self.base.attributes.get("sites")):
site["positions"] = list(positions[index])
for index, site in enumerate(self.sites):
site.positions = list(positions[index])

# The orthorhombic cell that (just) accomodates the whole structure is now given by the
# extremas of position in each dimension:
Expand Down Expand Up @@ -977,10 +1021,14 @@ def _get_object_phonopyatoms(self):
"""
from phonopy.structure.atoms import PhonopyAtoms

atoms = PhonopyAtoms(symbols=[_.kinds for _ in self.properties.sites])
# Phonopy internally uses scaled positions, so you must store cell first!
atoms.set_cell(self.properties.cell)
atoms.set_positions([_.position for _ in self.properties.sites])
atoms = PhonopyAtoms(
symbols = self.properties.symbols,
masses = self.properties.masses,
magnetic_moments = self.properties.magmoms,
positions = self.properties.positions,
cell = self.cell,
pbc = self.pbc,
)

return atoms

Expand All @@ -993,7 +1041,10 @@ def _get_object_ase(self):
"""
import ase

asecell = ase.Atoms(cell=self.properties.cell, pbc=self.properties.pbc)
asecell = ase.Atoms(
cell=self.properties.cell,
pbc=self.properties.pbc,
)

for site in self.properties.sites:
asecell.append(site.to_ase(kinds=site.kinds))
Expand Down Expand Up @@ -1470,27 +1521,11 @@ def add_atom(self, index=-1, **atom_info):
sites.append(new_site.model_dump())
structure["sites"] = sites
self.__init__(**structure)
'''else:
"""Update the site at the given index."""
for key, value in new_site.model_dump(exclude_defaults=True).items():
_value = getattr(self.properties, key, None)
print(len(self.properties.sites))
print(key, _value)
if not _value:
print(len(self.properties.sites))
if key in self.get_supported_properties():
print(len(self.properties.sites))
# first, we need to populate a list, so we can insert/append the new value
print([_DEFAULT_VALUES[key]],(len(self.properties.sites)))
setattr(self.properties, key, [_DEFAULT_VALUES[key]]*(len(self.properties.sites)))
_value = getattr(self.properties, key, None)
else:
raise ValueError(f"Invalid key '{key}' for site properties.")
if index > -1:
_value.insert(index, value)
else:
_value.append(value)
print(key, _value)'''
return

def append_atom(self, **atom_info):
"""Append a new atom to the structure."""
self.add_atom(**atom_info)
return

def pop_atom(self, index=-1):
Expand Down
2 changes: 1 addition & 1 deletion src/aiida_atomistic/data/structure/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ def check_minimal_requirements(cls, data):

if not data.get("cell", None):
# raise ValueError("The structure must contain a cell")
#warnings.warn("using default cell")
warnings.warn("using default cell")
data["cell"] = _DEFAULT_CELL
if not data.get("pbc", None):
# raise ValueError("The structure must contain periodic boundary conditions")
Expand Down
16 changes: 14 additions & 2 deletions src/aiida_atomistic/data/structure/site.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,16 @@ def check_minimal_requirements(cls, data):

return data

# Start of redundant properties to make easier plugin migrations
@property
def kind_name(self):
return self.kinds

@property
def position(self):
return self.positions
# End of redundant properties

@property
def is_alloy(self):
"""Return whether the Site is an alloy, i.e. contains more than one element
Expand Down Expand Up @@ -261,11 +271,13 @@ def to_ase(self, kinds):
# we should put a small routine to do tags. or instead of kinds, provide the tag (or tag mapping).
tag = None
atom_dict = self.model_dump()
atom_dict["symbols"] = atom_dict.pop("symbols", None)
atom_dict["positions"] = atom_dict.pop("positions", None)
atom_dict["symbol"] = atom_dict.pop("symbols", None)
atom_dict["position"] = atom_dict.pop("positions", None)
atom_dict["magmom"] = atom_dict.pop("magmoms", None)
atom_dict["momentum"] = atom_dict.pop("momenta", None)
atom_dict["charge"] = atom_dict.pop("charges", None)
atom_dict["mass"] = atom_dict.pop("masses", None)
atom_dict["tag"] = atom_dict.pop("kinds", None)
for prop in set(self.model_dump().keys()).difference(required_properties):
atom_dict.pop(prop,None)
aseatom = ase.Atom(
Expand Down
32 changes: 32 additions & 0 deletions tests/data/test_structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ def test_structure_initialization(example_structure_dict):
), f"Expected type for empty StructureDataMutable: {type(StructureDataMutable)}, \
received: {type(structure)}"

# (1.1.1) Empty StructureDataMutable apart cell
structure = StructureDataMutable()
structure.set_cell([[1.0,0.0,0.0],[0.0,1.0,0.0],[0.0,0.0,1.0]])
assert structure.properties.cell == [[1.0,0.0,0.0],[0.0,1.0,0.0],[0.0,0.0,1.0]]

# (1.2)
for structure_type in [StructureDataMutable, StructureData]:
structure = structure_type(**example_structure_dict)
Expand All @@ -49,6 +54,33 @@ def test_structure_database_attributes(example_structure_dict):
structure = StructureData(**example_structure_dict)
assert structure.get_defined_properties(exclude_computed=False).difference(set(structure.base.attributes.all.keys())) == {'sites'}

# Test the redundant methods for the StructureData class.
def test_redundant(example_structure_dict):
for structure_type in [StructureDataMutable, StructureData]:
structure = structure_type(**example_structure_dict)

assert structure.properties.pbc == structure.pbc
assert structure.properties.cell == structure.cell
assert structure.properties.sites[0].kinds == structure.sites[0].kind_name
assert structure.properties.sites[0].positions == structure.sites[0].position

def test_RedundantKind(example_structure_dict):

from aiida_atomistic.data.structure.mixin import RedundantKind
for structure_type in [StructureDataMutable, StructureData]:
structure = structure_type(**example_structure_dict)

assert any([isinstance(kind, RedundantKind) for kind in structure.kinds])
assert structure.properties.sites[0].kinds == structure.sites[0].kind_name
assert structure.properties.sites[0].positions == structure.sites[0].position

for kind, site in zip(structure.kinds, structure.properties.sites):
kind.mass = site.masses
kind.symbol = site.symbols
kind.weights = site.weights
kind.name = site.kinds


# StructureData methods:

def test_dict(example_structure_dict,example_dumped_structure_dict):
Expand Down
Loading