#!/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 create inputs from existing CRYSTAL17 runs
"""
import os
import tempfile
from aiida.common.exceptions import OutputParsingError
from aiida.common.folders import SandboxFolder
from aiida.plugins import CalculationFactory, DataFactory
import ase
from aiida_crystal17.parsers.raw import crystal_stdout
from aiida_crystal17.parsers.raw.inputd12_read import extract_data
# pylint: disable=too-many-locals
[docs]def populate_builder(remote_data, code=None, metadata=None):
"""create ``crystal17.main`` input nodes from an existing run
NB: none of the nodes are stored, also
existing basis will be retrieved if availiable
Parameters
----------
folder: aiida.common.folders.Folder or str
folder containing the input and output files
remote_data: aiida.orm.RemoteData
containing the input and output files required for parsing
code: str or aiida.orm.nodes.data.code.Code or None
metadata: dict or None
calculation metadata
Returns
-------
aiida.engine.processes.ProcessBuilder
"""
calc_cls = CalculationFactory("crystal17.main")
basis_cls = DataFactory("crystal17.basisset")
struct_cls = DataFactory("structure")
symmetry_cls = DataFactory("crystal17.symmetry")
kind_cls = DataFactory("crystal17.kinds")
# get files
in_file_name = calc_cls.spec_options.get("input_file_name").default
out_file_name = calc_cls.spec_options.get("output_main_file_name").default
if metadata and "options" in metadata:
in_file_name = metadata["options"].get("input_file_name", in_file_name)
out_file_name = metadata["options"].get("output_main_file_name", out_file_name)
remote_files = remote_data.listdir()
if in_file_name not in remote_files:
raise IOError(
"The input file '{}' is not contained in the remote_data folder. "
"If it has a different name, change "
"metadata['options]['input_file_name']".format(in_file_name)
)
if out_file_name not in remote_files:
raise IOError(
"The output file '{}' is not contained in the remote_data folder. "
"If it has a different name, change "
"metadata['options]['output_main_file_name']".format(out_file_name)
)
with SandboxFolder() as folder:
remote_data.getfile(in_file_name, os.path.join(folder.abspath, in_file_name))
with folder.open(in_file_name, mode="r") as handle:
param_dict, basis_sets, atom_props = extract_data(handle.read())
remote_data.getfile(out_file_name, os.path.join(folder.abspath, out_file_name))
with folder.open(out_file_name, mode="r") as handle:
try:
data = crystal_stdout.read_crystal_stdout(handle.read())
except IOError as err:
raise OutputParsingError(
"Error in CRYSTAL 17 run output: {}".format(err)
)
# we retrieve the initial primitive geometry and symmetry
atoms = _create_atoms(data, "initial_geometry")
# convert fragment (i.e. unfixed) to fixed
if "fragment" in atom_props:
frag = atom_props.pop("fragment")
atom_props["fixed"] = [
i + 1 for i in range(atoms.get_number_of_atoms()) if i + 1 not in frag
]
atoms.set_tags(_create_tags(atom_props, atoms))
structure = struct_cls(ase=atoms)
if atom_props:
kind_names = structure.get_kind_names()
kinds_dict = {"kind_names": kind_names}
for key, atom_indexes in atom_props.items():
kv_map = {
kn: i + 1 in atom_indexes
for i, kn in enumerate(structure.get_site_kindnames())
}
kinds_dict[key] = [kv_map[kn] for kn in kind_names]
kinds = kind_cls(data=kinds_dict)
else:
kinds = None
symmetry = symmetry_cls(
data={
"operations": data["initial_geometry"]["primitive_symmops"],
"basis": "fractional",
"hall_number": None,
}
)
bases = {}
for bset in basis_sets:
bfile = tempfile.NamedTemporaryFile(delete=False)
try:
with open(bfile.name, "w") as f:
f.write(bset)
bdata, _ = basis_cls.get_or_create(
bfile.name, use_first=False, store_basis=False
)
# TODO report if bases created or retrieved
finally:
os.remove(bfile.name)
bases[bdata.element] = bdata
builder = calc_cls.create_builder(
param_dict,
structure,
bases,
symmetry=symmetry,
kinds=kinds,
code=code,
metadata=metadata,
)
return builder
[docs]def _create_atoms(data, section):
"""create ase.Atoms from stdout parsed data"""
cell_data = data[section]["primitive_cell"]
cell_vectors = []
for n in "a b c".split():
cell_vectors.append(cell_data["cell_vectors"][n])
ccoords = cell_data["ccoords"]
atoms = ase.Atoms(
cell=cell_vectors,
pbc=cell_data["pbc"],
symbols=cell_data["symbols"],
positions=ccoords,
)
return atoms