Source code for aiida_crystal17.calculations.prop_abstract

#!/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.
"""Plugin for running CRYSTAL17 properties computations."""
import os

from aiida.common.datastructures import CalcInfo, CodeInfo
from aiida.engine import CalcJob, CalcJobProcessSpec
from aiida.orm import FolderData, RemoteData, SinglefileData
from aiida.plugins import DataFactory


[docs]class PropAbstractCalculation(CalcJob): """Abstract AiiDA calculation plugin class, to run the properties17 executable. Subclasses must at least override methods: - ``define``; specifying a parser and additional input/output nodes and exit codes. - ``validate_parameters`` - ``create_input_content`` - ``get_retrieve_list`` - ``get_retrieve_temp_list`` """ link_output_results = "results" requires_newk = True
[docs] @classmethod def validate_parameters(cls, data, _): raise NotImplementedError
[docs] @staticmethod def create_newk_lines(dct): """Create NEWK section of input file.""" k_is, k_isp = dct["k_points"] lines = ["NEWK"] if isinstance(k_is, int): lines.append("{0} {1}".format(k_is, k_isp)) else: lines.append("0 {0}\n".format(k_isp)) lines.append("{0} {1} {2}\n".format(k_is[0], k_is[1], k_is[2])) lines.append("1 0") # 1 = Fermi energy is computed return lines
[docs] def create_input_content(self): raise NotImplementedError
[docs] def get_retrieve_list(self): raise NotImplementedError
[docs] def get_retrieve_temp_list(self): raise NotImplementedError
[docs] @classmethod def define(cls, spec: CalcJobProcessSpec): super(PropAbstractCalculation, cls).define(spec) spec.input("metadata.options.input_file_name", valid_type=str, default="INPUT") spec.input("metadata.options.input_wf_name", valid_type=str, default="fort.9") spec.input( "metadata.options.stdout_file_name", valid_type=str, default="main.out" ) spec.input( "wf_folder", valid_type=(FolderData, RemoteData, SinglefileData), required=True, help="the folder containing the wavefunction fort.9 file", ) spec.input( "parameters", valid_type=DataFactory("dict"), required=True, validator=cls.validate_parameters, help="the input parameters to create the properties input file.", ) # subclasses should implement # spec.input('metadata.options.parser_name', valid_type=str, default='crystal17.') # Unrecoverable errors: resources like the retrieved folder or its expected contents are missing spec.exit_code( 200, "ERROR_NO_RETRIEVED_FOLDER", message="The retrieved folder data node could not be accessed.", ) spec.exit_code( 210, "ERROR_OUTPUT_FILE_MISSING", message="the main (stdout) output file was not found", ) spec.exit_code( 211, "ERROR_TEMP_FOLDER_MISSING", message="the temporary retrieved folder was not found", ) # Unrecoverable errors: required retrieved files could not be read, parsed or are otherwise incomplete spec.exit_code( 300, "ERROR_PARSING_STDOUT", message=( "An error was flagged trying to parse the " "crystal exec stdout file" ), ) spec.exit_code( 350, "ERROR_CRYSTAL_INPUT", message="the input file could not be read by CRYSTAL", ) spec.exit_code( 351, "ERROR_WAVEFUNCTION_NOT_FOUND", message="CRYSTAL could not find the required wavefunction file", ) spec.exit_code( 352, "UNIT_CELL_NOT_NEUTRAL", message="Possibly due to erroneous CHEMOD basis set modification", ) spec.exit_code( 353, "SHELL_SYMMETRY_ERROR", message="Possibly due to erroneous CHEMOD basis set modification", ) spec.exit_code( 354, "CHEMMOD_ERROR", message="Error in CHEMOD basis set modification" ) # Significant errors but calculation can be used to restart spec.exit_code( 400, "ERROR_OUT_OF_WALLTIME", message="The calculation stopped prematurely because it ran out of walltime.", ) spec.exit_code( 401, "ERROR_OUT_OF_MEMORY", message="The calculation stopped prematurely because it ran out of memory.", ) spec.exit_code( 402, "ERROR_OUT_OF_VMEMORY", message="The calculation stopped prematurely because it ran out of virtual memory.", ) spec.exit_code( 413, "BASIS_SET_LINEARLY_DEPENDENT", message="an error encountered usually during geometry optimisation", ) spec.exit_code( 414, "ERROR_SCF_ABNORMAL_END", message="an error was encountered during an SCF computation", ) spec.exit_code( 415, "ERROR_MPI_ABORT", message="an unknown error was encountered, causing the MPI to abort", ) spec.exit_code( 499, "ERROR_CRYSTAL_RUN", message="The main crystal output file flagged an unhandled error", ) spec.output( cls.link_output_results, valid_type=DataFactory("dict"), required=True, help="Summary Data extracted from the output file(s)", ) spec.default_output_node = cls.link_output_results
[docs] def prepare_for_submission(self, tempfolder): """This is the routine to be called when you want to create the input files and related stuff with a plugin. :param tempfolder: an aiida.common.folders.Folder subclass where the plugin should put all its files. """ input_content = self.create_input_content() with tempfolder.open(self.metadata.options.input_file_name, "w") as f: f.write(input_content) remote_files = None local_copy_list = None if isinstance(self.inputs.wf_folder, FolderData): local_copy_list = [ ( self.inputs.wf_folder.uuid, self.metadata.options.input_wf_name, "fort.9", ) ] elif isinstance(self.inputs.wf_folder, SinglefileData): local_copy_list = [ (self.inputs.wf_folder.uuid, self.inputs.wf_folder.filename, "fort.9") ] else: remote_files = [ ( self.inputs.wf_folder.computer.uuid, os.path.join( self.inputs.wf_folder.get_remote_path(), self.metadata.options.input_wf_name, ), "fort.9", ) ] return self.create_calc_info( tempfolder, local_copy_list=local_copy_list, remote_copy_list=remote_files, retrieve_list=self.get_retrieve_list(), retrieve_temporary_list=self.get_retrieve_temp_list(), )
[docs] def create_calc_info( self, tempfolder, local_copy_list=None, remote_copy_list=None, remote_symlink_list=None, retrieve_list=None, retrieve_temporary_list=None, ): """Prepare CalcInfo object for aiida, to describe how the computation will be executed and recovered """ # Prepare CodeInfo object for aiida, # describes how a code has to be executed codeinfo = CodeInfo() codeinfo.code_uuid = self.inputs.code.uuid if self.metadata.options.withmpi: # parallel versions of crystal (Pcrystal, Pproperties & MPPcrystal) # read data specifically from a file called INPUT if self.metadata.options.input_file_name != "INPUT": tempfolder.insert_path( os.path.join( tempfolder.abspath, self.metadata.options.input_file_name ), dest_name="INPUT", ) else: codeinfo.stdin_name = self.metadata.options.input_file_name codeinfo.stdout_name = self.metadata.options.stdout_file_name # serial version output to stdout, but parallel version output to stderr! # so we join the files codeinfo.join_files = True codeinfo.cmdline_params = [] codeinfo.withmpi = self.metadata.options.withmpi # Prepare CalcInfo object for aiida calcinfo = CalcInfo() calcinfo.uuid = self.uuid calcinfo.codes_info = [codeinfo] calcinfo.local_copy_list = local_copy_list or [] calcinfo.remote_copy_list = remote_copy_list or [] calcinfo.remote_symlink_list = remote_symlink_list or [] calcinfo.retrieve_list = retrieve_list or [] calcinfo.retrieve_temporary_list = retrieve_temporary_list or [] return calcinfo