#!/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.
"""
module to write CRYSTAL17 .d12 files
"""
from aiida_crystal17.common import get_keys
from aiida_crystal17.validation import validate_against_schema
# TODO check float format and rounding, e.g. "{}".format(0.00001) -> 1e-05, can CRYSTAL handle that?
# TODO FIELD/FIELDCON
# TODO FREQCALC
# TODO ANHARM
# TODO EOS
# TODO incompatibility tests e.g. using ATOMSPIN without SPIN (and spin value of SPINLOCK)
# TODO look at https://gitlab.com/ase/ase/blob/master/ase/calculators/crystal.py to see if anything can be used
[docs]def _hamiltonian_block(outstr, indict, atom_props):
# Hamiltonian Optional Keywords
outstr += format_value(indict, ["scf", "single"])
# DFT Optional Block
if get_keys(indict, ["scf", "dft"], False):
outstr += "DFT\n"
xc = get_keys(indict, ["scf", "dft", "xc"], raise_error=True)
if isinstance(xc, (tuple, list)):
if len(xc) == 2:
outstr += "CORRELAT\n"
outstr += "{}\n".format(xc[0])
outstr += "EXCHANGE\n"
outstr += "{}\n".format(xc[1])
else:
outstr += format_value(indict, ["scf", "dft", "xc"])
if get_keys(indict, ["scf", "dft", "SPIN"], False):
outstr += "SPIN\n"
outstr += format_value(indict, ["scf", "dft", "grid"])
outstr += format_value(indict, ["scf", "dft", "grid_weights"])
outstr += format_value(indict, ["scf", "dft", "numerical"])
outstr += "END\n"
if get_keys(indict, ["scf", "dft", "d3"], False):
outstr += "DFTD3\n"
outstr += format_value(indict, ["scf", "dft", "d3"])
outstr += "END\n"
# # K-POINTS (SHRINK\nPMN Gilat)
k_is, k_isp = get_keys(indict, ["scf", "k_points"], raise_error=True)
outstr += "SHRINK\n"
if isinstance(k_is, int):
outstr += "{0} {1}\n".format(k_is, k_isp)
else:
outstr += "0 {0}\n".format(k_isp)
outstr += "{0} {1} {2}\n".format(k_is[0], k_is[1], k_is[2])
# RESTART
if get_keys(indict, ["scf", "GUESSP"], False):
outstr += "GUESSP\n"
# ATOMSPIN
spins = []
for anum in atom_props.get("spin_alpha", []):
spins.append((anum, 1))
for anum in atom_props.get("spin_beta", []):
spins.append((anum, -1))
if spins:
outstr += "ATOMSPIN\n"
outstr += "{}\n".format(len(spins))
for anum, spin in sorted(spins):
outstr += "{0} {1}\n".format(anum, spin)
# SCF/Other Optional Keywords
outstr += format_value(indict, ["scf", "numerical"])
outstr += format_value(indict, ["scf", "fock_mixing"])
outstr += format_value(indict, ["scf", "spinlock"])
for keyword in sorted(get_keys(indict, ["scf", "post_scf"], [])):
outstr += "{}\n".format(keyword)
# Hamiltonian and SCF End
outstr += "END\n"
return outstr
[docs]def _geometry_block(outstr, indict, atom_props):
# Geometry
outstr += "EXTERNAL\n" # we assume external geometry
# Geometry Optional Keywords (including optimisation)
for keyword in get_keys(indict, ["geometry", "info_print"], []):
outstr += "{}\n".format(keyword)
for keyword in get_keys(indict, ["geometry", "info_external"], []):
outstr += "{}\n".format(keyword)
if "ROTCRY" in indict.get("geometry", {}):
outstr += "ROTCRY\n"
rotcry = indict["geometry"]["ROTCRY"]
if rotcry is None:
outstr += "AUTO\n"
elif isinstance(rotcry[0], (int, float)):
outstr += "ANGROT\n"
outstr += "{0:.6f} {1:.6f} {2:.6f}\n".format(*rotcry)
else:
outstr += "MATROT\n"
for row in rotcry:
outstr += "{0:.6f} {1:.6f} {2:.6f}\n".format(*row)
if indict.get("geometry", {}).get("optimise", False):
outstr += "OPTGEOM\n"
outstr += format_value(indict, ["geometry", "optimise", "type"])
unfixed = atom_props.get("unfixed", [])
if unfixed:
outstr += "FRAGMENT\n"
outstr += "{}\n".format(len(unfixed))
outstr += " ".join([str(a) for a in sorted(unfixed)]) + "\n"
outstr += format_value(indict, ["geometry", "optimise", "hessian"])
outstr += format_value(indict, ["geometry", "optimise", "gradient"])
for keyword in sorted(
get_keys(indict, ["geometry", "optimise", "info_print"], [])
):
outstr += "{}\n".format(keyword)
outstr += format_value(indict, ["geometry", "optimise", "convergence"])
outstr += "ENDOPT\n"
# Geometry End
outstr += "END\n"
return outstr
[docs]def _basis_set_block(outstr, indict, basis_sets, atom_props):
# Basis Sets
if isinstance(basis_sets[0], str):
outstr += "\n".join([basis_set.strip() for basis_set in basis_sets])
else:
outstr += "\n".join([basis_set.content.strip() for basis_set in basis_sets])
outstr += "\n99 0\n"
# GHOSTS
ghosts = atom_props.get("ghosts", [])
if ghosts:
outstr += "GHOSTS\n"
outstr += "{}\n".format(len(ghosts))
outstr += " ".join([str(a) for a in sorted(ghosts)]) + "\n"
# CHEMOD
chemod = atom_props.get("chemod", {})
if chemod:
outstr += "CHEMOD\n"
outstr += "{}\n".format(len(chemod))
for anum in sorted(chemod.keys()):
outstr += str(anum) + "\n"
outstr += " ".join(["{:.6f}".format(v) for v in chemod[anum]]) + "\n"
# Basis Sets Optional Keywords
outstr += format_value(indict, ["basis_set"])
# Basis Sets End
outstr += "END\n"
return outstr
[docs]def create_atom_properties(structure, kinds_data=None):
"""create dict of properties for each atom
:param structure: ``StructureData``
:param kinds_data: ``KindData`` atom kind data for each atom
:return: dict of atom properties
:rtype: dict
"""
if kinds_data is None:
return {"spin_alpha": [], "spin_beta": [], "ghosts": []}
if set(kinds_data.data.kind_names) != set(structure.get_kind_names()):
raise AssertionError(
"kind names are different for structure data and kind data: "
"{0} != {1}".format(
set(structure.get_kind_names()), set(kinds_data.data.kind_names)
)
)
atom_props = {
"spin_alpha": [],
"spin_beta": [],
"fixed": [],
"unfixed": [],
"ghosts": [],
"chemod": {},
}
kind_dict = kinds_data.kind_dict
for i, kind_name in enumerate(structure.get_site_kindnames()):
if kind_dict[kind_name].get("spin_alpha", False):
atom_props["spin_alpha"].append(i + 1)
if kind_dict[kind_name].get("spin_beta", False):
atom_props["spin_beta"].append(i + 1)
if kind_dict[kind_name].get("ghost", False):
atom_props["ghost"].append(i + 1)
if kind_dict[kind_name].get("fixed", False):
atom_props["fixed"].append(i + 1)
if not kind_dict[kind_name].get("fixed", False):
atom_props["unfixed"].append(i + 1)
if kind_dict[kind_name].get("chemod", None) is not None:
atom_props["chemod"][i + 1] = kind_dict[kind_name]["chemod"]
# we only need unfixed if there are fixed
if not atom_props.pop("fixed"):
atom_props.pop("unfixed")
return atom_props