Source code for aiida_crystal17.data.struct_settings

import copy
import tempfile

import numpy as np
from aiida.common.exceptions import ValidationError
from aiida.common.extendeddicts import AttributeDict
from aiida.common.utils import classproperty
from aiida.orm import Data
from aiida_crystal17.parsers.geometry import CRYSTAL_TYPE_MAP, CENTERING_CODE_MAP
from aiida_crystal17.validation import validate_with_dict
from jsonschema import ValidationError as SchemeError


[docs]class StructSettingsData(Data): """ Stores input symmetry and kind specific setting for a structure (as required by CRYSTAL17) - symmetry operations are stored on file (in the style of ArrayData) - the rest of the values are stored as attributes in the database """ _ops_filename = "operations.npy" _data_schema = { "$schema": "http://json-schema.org/draft-04/schema#", "title": "CRYSTAL17 structure symmetry settings", "type": "object", "required": ["space_group", "crystal_type", "centring_code", "operations"], "additionalProperties": False, "properties": { "symmetry_program": { "description": "the program used to generate the symmetry", "type": "string" }, "symmetry_version": { "description": "the version of the program used to generate the symmetry", "type": "string" }, "computation_class": { "description": "the class used to compute the settings", "type": "string" }, "computation_version": { "description": "the version of the class used to compute the settings", "type": "string" }, "space_group": { "description": "Space group number (international)", "type": "integer", "minimum": 1, "maximum": 230 }, "crystal_type": { "description": "The crystal type, as designated by CRYSTAL17", "type": "integer", "minimum": 1, "maximum": 6 }, "centring_code": { "description": "The crystal type, as designated by CRYSTAL17", "type": "integer", "minimum": 1, "maximum": 6 }, "operations": { "description": "symmetry operations to use (in the fractional basis)", "type": ["null", "array"], "items": { "description": "each item should be a list of [r00,r10,r20,r01,r11,r21,r02,r12,r22,t0,t1,t2]", "type": "array", "minItems": 12, "maxItems": 12, "items": { "type": "number", "minimum": -1, "maximum": 1 } } }, "kinds": { "description": "settings for input properties of each species kind", "type": "object", "additionalProperties": False, "properties": { "spin_alpha": { "description": "kinds with initial alpha (+1) spin (set by ATOMSPIN)", "type": "array", "items": { "type": "string", "uniqueItems": True } }, "spin_beta": { "description": "kinds with initial beta (-1) spin (set by ATOMSPIN)", "type": "array", "items": { "type": "string", "uniqueItems": True } }, "fixed": { "description": "kinds with are fixed in position for optimisations (set by FRAGMENT)", "type": "array", "items": { "type": "string", "uniqueItems": True } }, "ghosts": { "description": "kinds which will be removed, but their basis set are left (set by GHOSTS)", "type": "array", "items": { "type": "string", "uniqueItems": True } } } }, } } @classproperty def data_schema(cls): return copy.deepcopy(cls._data_schema) def _validate(self): super(StructSettingsData, self)._validate() fname = self._ops_filename if fname not in self.get_folder_list(): raise ValidationError("operations not set") try: validate_with_dict(self.data, self._data_schema) except SchemeError as err: raise ValidationError(err)
[docs] def set_data(self, data): """ Replace the current data with another one. :param data: The dictionary to set. """ from aiida.common.exceptions import ModificationNotAllowed # first validate the inputs try: validate_with_dict(data, self._data_schema) except SchemeError as err: raise ValidationError(err) # store all but the symmetry operations as attributes old_dict = copy.deepcopy(dict(self.iterattrs())) attributes_set = False try: # Delete existing attributes self._del_all_attrs() # I set the keys self._update_attrs( {k: v for k, v in data.items() if k != "operations"}) self._set_attr("num_symops", len(data["operations"])) attributes_set = True finally: if not attributes_set: try: # Try to restore the old data self._del_all_attrs() self._update_attrs(old_dict) except ModificationNotAllowed: pass # store the symmetry operations on file self._set_operations(data["operations"])
def _update_attrs(self, data): """ Update the current attribute with the keys provided in the dictionary. :param data: a dictionary with the keys to substitute. It works like dict.update(), adding new keys and overwriting existing keys. """ for k, v in data.iteritems(): self._set_attr(k, v) def _set_operations(self, ops): fname = self._ops_filename if fname in self.get_folder_list(): self.remove_path(fname) with tempfile.NamedTemporaryFile() as f: # Store in a temporary file, and then add to the node np.save(f, ops) f.flush( ) # Important to flush here, otherwise the next copy command # will just copy an empty file super(StructSettingsData, self).add_path(f.name, fname) def _get_operations(self): fname = self._ops_filename if fname not in self.get_folder_list(): raise KeyError("symmetry operations not set for node pk={}".format( self.pk)) array = np.load(self.get_abs_path(fname)) return array.tolist() @property def data(self): """ Return the data as an AttributeDict """ data = dict(self.iterattrs()) if "num_symops" in data: data.pop("num_symops") data["operations"] = self._get_operations() return AttributeDict(data) @property def num_symops(self): return self.get_attr("num_symops", None) @property def space_group(self): return self.get_attr("space_group", None) @property def crystal_system(self): """get the string version of the crystal system (e.g. 'triclinic')""" ctype = self.get_attr('crystal_type') return CRYSTAL_TYPE_MAP[ctype] @property def crystallographic_transform(self): """get the primitive to crystallographic transformation matrix""" ctype = self.get_attr('centring_code') return CENTERING_CODE_MAP[ctype]
[docs] def add_path(self, src_abs, dst_path): from aiida.common.exceptions import ModificationNotAllowed raise ModificationNotAllowed( "Cannot add files or directories to StructSettingsData object")
[docs] def compare_operations(self, ops, decimal=5): """compare operations against stored ones :param ops: list of (flattened) symmetry operations :param decimal: number of decimal points to round values to :returns: dict of differences """ ops_orig = self._get_operations() # create a set for each ops_orig = set( [tuple([round(i, decimal) for i in op]) for op in ops_orig]) ops_new = set([tuple([round(i, decimal) for i in op]) for op in ops]) differences = {} if ops_orig.difference(ops_new): differences["missing"] = ops_orig.difference(ops_new) if ops_new.difference(ops_orig): differences["additional"] = ops_new.difference(ops_orig) return differences