#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright 2019 Chris Sewell
#
# This file is part of aiida-crystal17.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms and conditions
# of version 3 of the GNU Lesser General Public License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
"""a work flow to symmetrise a structure and compute the symmetry operations"""
import traceback
from aiida.engine import CalcJobProcessSpec, ExitCode, WorkChain, calcfunction
from aiida.orm import Str, StructureData
from aiida.orm.nodes.data.base import to_aiida_type
from aiida.plugins import DataFactory
from aiida_crystal17.data.symmetry import SymmetryData
from aiida_crystal17.symmetry import (
compute_symmetry_dict,
find_primitive,
reset_kind_names,
standardize_cell,
)
from aiida_crystal17.validation import validate_against_schema
[docs]@calcfunction
def standard_structure(structure, settings):
atol = settings.get_attribute("angle_tolerance", None)
return standardize_cell(
structure, settings["symprec"], atol, to_primitive=False, no_idealize=True
)
[docs]@calcfunction
def standard_primitive_structure(structure, settings):
atol = settings.get_attribute("angle_tolerance", None)
return standardize_cell(
structure, settings["symprec"], atol, to_primitive=True, no_idealize=True
)
[docs]@calcfunction
def standard_primitive_ideal_structure(structure, settings):
atol = settings.get_attribute("angle_tolerance", None)
return standardize_cell(
structure, settings["symprec"], atol, to_primitive=True, no_idealize=False
)
[docs]@calcfunction
def standard_ideal_structure(structure, settings):
atol = settings.get_attribute("angle_tolerance", None)
return standardize_cell(
structure, settings["symprec"], atol, to_primitive=False, no_idealize=False
)
[docs]@calcfunction
def primitive_structure(structure, settings):
atol = settings.get_attribute("angle_tolerance", None)
return find_primitive(structure, settings["symprec"], atol)
[docs]@calcfunction
def change_kind_names(structure, settings):
return reset_kind_names(structure, settings["kind_names"])
[docs]@calcfunction
def compute_symmetry(structure, settings):
atol = settings.get_attribute("angle_tolerance", None)
use_kinds = settings.get_attribute("symmetry_usekinds", True)
data = compute_symmetry_dict(
structure, settings["symprec"], atol, use_kinds=use_kinds
)
return SymmetryData(data=data)
[docs]@calcfunction
def cif_to_structure(cif, converter=None):
if converter is None:
converter_type = "ase"
elif isinstance(converter, Str):
converter_type = converter.value
else:
return ExitCode(300, "ERROR_INVALID_CONVERTER_TYPE")
return cif.get_structure(converter=converter_type)
[docs]class Symmetrise3DStructure(WorkChain):
"""modify an AiiDa structure instance and compute its symmetry
Inequivalent atomic sites are dictated by atom kinds
"""
[docs] @classmethod
def define(cls, spec: CalcJobProcessSpec):
super(Symmetrise3DStructure, cls).define(spec)
spec.input("structure", valid_type=StructureData, required=False)
spec.input("cif", valid_type=DataFactory("cif"), required=False)
spec.input(
"settings",
valid_type=DataFactory("dict"),
serializer=to_aiida_type,
required=True,
validator=cls.validate_settings,
)
spec.outline(cls.validate_inputs, cls.compute)
spec.output("symmetry", valid_type=SymmetryData, required=True)
spec.output("structure", valid_type=StructureData, required=False)
spec.exit_code(
300,
"ERROR_INVALID_INPUT_RESOURCES",
message="one of either a structure or cif input must be supplied",
)
spec.exit_code(
301,
"ERROR_NON_3D_STRUCTURE",
message='the supplied structure must be 3D (i.e. have all dimensions pbc=True)"',
)
spec.exit_code(
302,
"ERROR_COMPUTE_OPTIONS",
message="idealize can only be used when standardize=True",
)
spec.exit_code(
303,
"ERROR_RESET_KIND_NAMES",
message="the kind names supplied are not compatible with the structure",
)
spec.exit_code(
304, "ERROR_NEW_STRUCTURE", message="error creating new structure"
)
spec.exit_code(
305,
"ERROR_COMPUTING_SYMMETRY",
message="error computing symmetry operations",
)
[docs] @classmethod
def get_settings_schema(cls):
return {
"$schema": "http://json-schema.org/draft-07/schema",
"type": "object",
"required": ["symprec"],
"properties": {
"cif_parser": {
"description": "Package for parsing cif to structures",
"type": "string",
"enum": ["ase", "pymatgen"],
"default": "ase",
},
"symprec": {
"description": (
"Length tolerance for symmetry finding: "
"0.01 is fairly strict and works well for properly refined structures"
),
"default": 0.01,
"type": "number",
"exclusiveMinimum": 0,
},
"angle_tolerance": {
"description": "Angle tolerance for symmetry finding, in the unit of angle degrees",
"default": None,
"type": ["number", "null"],
"exclusiveMinimum": 0,
},
"kind_names": {
"description": "a list of kind names, to assign each Site of the StructureData",
"type": "array",
"minItems": 1,
"items": {"type": "string"},
},
"compute_primitive": {
"description": "whether to convert the structure to its primitive form",
"type": "boolean",
"default": False,
},
"standardize_cell": {
"description": (
"whether to standardize the structure, see "
"https://atztogo.github.io/spglib/definition.html#conventions-of-standardized-unit-cell"
),
"type": "boolean",
"default": False,
},
"idealize_cell": {
"description": (
"whether to remove distortions of the unit cell's atomic positions, "
"using obtained symmetry operations"
),
"type": "boolean",
"default": False,
},
"symmetry_usekinds": {
"description": "if True use kind names to define inequivalent sites, else use symbols",
"type": "boolean",
"default": True,
},
},
}
[docs] @classmethod
def validate_settings(cls, settings_data, _):
settings_dict = settings_data.get_dict()
validate_against_schema(settings_dict, cls.get_settings_schema())
[docs] def compute(self):
structure = self.ctx.structure
if self.ctx.kind_names is not None:
try:
structure = change_kind_names(structure, self.inputs.settings)
except AssertionError as err:
traceback.print_exc()
self.logger.error("reset_kind_names: {}".format(err))
return self.exit_codes.ERROR_RESET_KIND_NAMES
self.ctx.new_structure = True
try:
if self.ctx.standardize_cell:
if self.ctx.compute_primitive and self.ctx.idealize_cell:
structure = standard_primitive_ideal_structure(
structure, self.inputs.settings
)
self.ctx.new_structure = True
elif self.ctx.compute_primitive:
structure = standard_primitive_structure(
structure, self.inputs.settings
)
self.ctx.new_structure = True
elif self.ctx.idealize_cell:
structure = standard_ideal_structure(
structure, self.inputs.settings
)
self.ctx.new_structure = True
else:
structure = standard_structure(structure, self.inputs.settings)
self.ctx.new_structure = True
elif self.ctx.compute_primitive:
structure = primitive_structure(structure, self.inputs.settings)
self.ctx.new_structure = True
except Exception as err:
traceback.print_exc()
self.logger.error("structure creation: {}".format(err))
return self.exit_codes.ERROR_NEW_STRUCTURE
if self.ctx.new_structure:
self.out("structure", structure)
try:
symmetry = compute_symmetry(structure, self.inputs.settings)
except Exception as err:
traceback.print_exc()
self.logger.error("symmetry computation: {}".format(err))
return self.exit_codes.ERROR_COMPUTING_SYMMETRY
self.out("symmetry", symmetry)