Source code for aiida_gulp.parsers.raw.write_input

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright 2019 Chris Sewell
#
# This file is part of aiida-gulp.
#
# 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.
import hashlib
import io

import six

from aiida_gulp.validation import validate_against_schema
from aiida_gulp.unit_styles import get_pressure
from aiida_gulp.parsers.raw.write_geometry import create_geometry_lines


[docs]class InputCreationBase(object): """a base class for creating a main.gin input file for GULP. Sub-classes can override the methods: ``validate_parameters``, ``get_input_keywords``, ``get_input_keywords`` """ def __init__(self, outputs=None): """create a main.gin input file for GULP Parameters ---------- outputs : dict or None mapping of type of output to filename, e.g. {"cif": "output.cif"} """ self._outputs = {} if outputs is None else outputs self._content_lines = None self._encoding = "utf-8"
[docs] def get_content(self): if self._content_lines is None: raise ValueError("content has not been set") return "\n".join(self._content_lines)
[docs] def get_content_lines(self): if self._content_lines is None: raise ValueError("content has not been set") return self._content_lines[:]
[docs] def get_content_hash(self): """get md5 hex hash of content""" content = self.get_content() return hashlib.md5(six.u(content).encode(self._encoding)).hexdigest()
[docs] def write_content(self, file_like): """write the content to file_like object (path string or object with `write` method) """ content = six.u(self.get_content()) if isinstance(file_like, six.string_types): with io.open(file_like, "w", encoding=self._encoding) as handle: handle.write(content) else: file_like.write(content)
[docs] def validate_parameters(self, parameters): """validate the parameters dict, supplied to ``create_content`` Parameters ---------- parameters : dict the paramaters dict Returns ------- bool True if validation is successful """ return True
[docs] def get_input_keywords(self, parameters): """return list of keywords for header, e.g. 'verb': verbose detail, including energy contributions 'operators': prints out symmetry operations 'prop': print properties, incl bulk/shear modulus, dielectric 'linmin': print details of minimisation 'comp': print intital/final geometry comparison Parameters ---------- parameters : dict the paramaters dict Returns ------- list[str] list of keywords """ return ["verb"]
[docs] def get_other_option_lines(self, parameters): """get list of other option lines for .gin Parameters ---------- parameters : dict additional parameter data Returns ------- list[str] """ return []
[docs] def create_content( self, structure, potential_lines, parameters=None, symmetry=None ): """create main input content for gulp.in Parameters ---------- structure : aiida.StructureData the input structure potential_lines: list[str] parameters : aiida.orm.nodes.data.dict.Dict or dict additional parameter data, by default None symmetry : aiida.orm.nodes.data.dict.Dict or dict data regarding the structure symmetry, by default None Returns ------- list[str] file content """ # convert inputs to dictionaries if parameters is None: parameters = {} else: if hasattr(parameters, "get_dict"): parameters = parameters.get_dict() if symmetry is not None: if hasattr(symmetry, "get_dict"): symmetry = symmetry.get_dict() # validation self.validate_parameters(parameters) content = [] # keywords content.append(" ".join(self.get_input_keywords(parameters))) content.append("") # TITLE if "title" in parameters: content.append("title") content.append("{}".format(parameters["title"])) content.append("end") content.append("") # GEOMETRY content.append("# Geometry") content.extend(self.get_geometry_lines(structure, symmetry)) content.append("") # TODO kind specific inputs (e.g. initial charge)? # FORCE FIELD content.append("# Force Field") content.extend(potential_lines) content.append("") # OTHER OPTIONS other_opts = self.get_other_option_lines(parameters) if other_opts: content.append("# Other Options") content.extend(other_opts) content.append("") # EXTERNAL OUTPUT OPTIONS if self._outputs: content.append("# External Outputs") for out_type, fname in self._outputs.items(): content.append("output {0} {1}".format(out_type, fname)) content.append("") self._content_lines = content return content
[docs] @staticmethod def get_geometry_lines(structure_data, symmetry_data=None): """ create list of lines for geometry section of .gin Parameters ---------- structure_data: aiida.StructureData or dict or ase.Atoms dict with keys: 'pbc', 'atomic_numbers', 'ccoords', 'lattice', or ase.Atoms, or any object that has method structure_data.get_ase() symmetry_data: dict or None keys; 'operations', 'basis', 'crystal_type_name'/'hall_number' Returns ------- list[str] """ return create_geometry_lines(structure_data, symmetry_data=symmetry_data)
[docs]class InputCreationSingle(InputCreationBase): pass
[docs]class InputCreationOpt(InputCreationBase):
[docs] def validate_parameters(self, parameters): validate_against_schema(parameters, "gulp_optimize.schema.json")
[docs] def get_input_keywords(self, parameters): keywords = ["optimise", "verb", parameters["relax"]["type"]] if parameters["minimize"]["style"] != "nr": keywords.append(parameters["minimize"]["style"]) # TODO set energy units: eV by default, or use keywords: kcal, kjmol # TODO switch between symmetric and non-symmetric # if not params.get('symmetry', True): # # Switches off symmetry after generating unit cell # keywords.append('nosymmetry') # 'full' keyword causes the nosymmetry keyword to produce the full, # instead of the primitive, unit cell. return keywords
[docs] def get_other_option_lines(self, parameters): lines = [] if parameters["relax"].get("pressure", False): pressure, punits = get_pressure( parameters["relax"]["pressure"], parameters["units"] ) lines.append("pressure {0:.4f} {1}".format(pressure, punits)) # NB: Causes energy to be replaced by enthalpy in calculations. # maximum number of optimisation steps (default 1000) if "max_iterations" in parameters["minimize"]: lines.append( "maxcyc opt {}".format(parameters["minimize"]["max_iterations"]) ) # TODO how do these compare to tolerances from LAMMPS? # maximum parameter tolerance (default 0.00001) # xtol opt 0.00001 # maximum function tolerance (default 0.00001) # ftol opt 0.00001 # maximum gradient tolerance (default 0.001) # gtol opt 0.001 # NB: ftol should always be less than gtol # maximum allowed individual gradient component (default 0.01) # gmax opt 0.01 return lines