From ed80b8c8abd1be35f1c4f870c15ebbbe703ea5b3 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Mon, 30 Mar 2020 21:31:58 +0200 Subject: [PATCH 1/6] Drop Python2 support. (#95) * Remove python2 things from setup.json. * Remove all `from __future__ ...` statements. * Remove `modernizer` hook from the pre-commit config. * Remove `six` and all the references in the code. --- .ci/check_travis_tag.py | 2 - .pre-commit-config.yaml | 11 ----- .pylintrc | 4 -- aiida_cp2k/calculations/__init__.py | 15 ++---- aiida_cp2k/parsers/__init__.py | 1 - aiida_cp2k/utils/__init__.py | 1 - aiida_cp2k/utils/input_generator.py | 15 ++---- aiida_cp2k/utils/parser.py | 3 -- aiida_cp2k/utils/workchains.py | 1 - aiida_cp2k/workchains/aiida_base_restart.py | 1 - aiida_cp2k/workchains/base.py | 1 - conftest.py | 1 - examples/single_calculations/example_dft.py | 3 -- .../example_dft_atomic_kinds.py | 3 -- .../single_calculations/example_failure.py | 2 - examples/single_calculations/example_geopt.py | 2 - examples/single_calculations/example_mm.py | 2 - .../example_multiple_force_eval.py | 3 -- .../single_calculations/example_no_struct.py | 3 -- .../single_calculations/example_precision.py | 2 - .../single_calculations/example_restart.py | 2 - .../example_structure_through_file.py | 4 -- examples/single_calculations/fixme_bands.py | 3 -- examples/workchains/example_base.py | 3 -- setup.json | 46 +++++++++---------- setup.py | 1 - test/test_input_generator.py | 2 - test/test_version_agreement.py | 2 - 28 files changed, 29 insertions(+), 110 deletions(-) diff --git a/.ci/check_travis_tag.py b/.ci/check_travis_tag.py index 5f37bbf1..403eeb68 100755 --- a/.ci/check_travis_tag.py +++ b/.ci/check_travis_tag.py @@ -7,8 +7,6 @@ # For further information on the license, see the LICENSE.txt file. # ############################################################################### """Check travis tag""" -from __future__ import print_function -from __future__ import absolute_import import os import sys diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f1b3e02a..b68d1ee1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,17 +1,6 @@ # # Install pre-commit hooks via # pre-commit install -# modernizer: make sure our code-base is Python 3 ready -- repo: https://github.com/python-modernize/python-modernize.git - sha: a234ce4e185cf77a55632888f1811d83b4ad9ef2 - hooks: - - id: python-modernize - exclude: ^docs/ - args: - - --write - - --nobackups - - --nofix=dict_six - - repo: local hooks: # yapf = yet another python formatter diff --git a/.pylintrc b/.pylintrc index 87a43206..ffbf1732 100644 --- a/.pylintrc +++ b/.pylintrc @@ -273,10 +273,6 @@ ignored-argument-names=_.*|^ignored_|^unused_ # Tells whether we should check for unused import in __init__ files. init-import=no -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six.moves,future.builtins - [MISCELLANEOUS] diff --git a/aiida_cp2k/calculations/__init__.py b/aiida_cp2k/calculations/__init__.py index d9ec4972..aa459101 100644 --- a/aiida_cp2k/calculations/__init__.py +++ b/aiida_cp2k/calculations/__init__.py @@ -7,11 +7,7 @@ ############################################################################### """AiiDA-CP2K input plugin.""" -from __future__ import absolute_import - import io -import six -from six.moves import map from aiida.engine import CalcJob from aiida.orm import Computer, Dict, SinglefileData, StructureData, RemoteData, BandsData @@ -51,16 +47,13 @@ def define(cls, spec): dynamic=True) # Specify default parser. - spec.input('metadata.options.parser_name', - valid_type=six.string_types, - default=cls._DEFAULT_PARSER, - non_db=True) + spec.input('metadata.options.parser_name', valid_type=str, default=cls._DEFAULT_PARSER, non_db=True) # Add input_filename attribute. - spec.input('metadata.options.input_filename', valid_type=six.string_types, default=cls._DEFAULT_INPUT_FILE) + spec.input('metadata.options.input_filename', valid_type=str, default=cls._DEFAULT_INPUT_FILE) # Add output_filename attribute. - spec.input('metadata.options.output_filename', valid_type=six.string_types, default=cls._DEFAULT_OUTPUT_FILE) + spec.input('metadata.options.output_filename', valid_type=str, default=cls._DEFAULT_OUTPUT_FILE) # Use mpi by default. spec.input('metadata.options.withmpi', valid_type=bool, default=True) @@ -129,7 +122,7 @@ def prepare_for_submission(self, folder): try: fobj.write(inp.render()) except ValueError as exc: - six.raise_from(InputValidationError("invalid keys or values in input parameters found"), exc) + raise InputValidationError("Invalid keys or values in input parameters found") from exc settings = self.inputs.settings.get_dict() if 'settings' in self.inputs else {} diff --git a/aiida_cp2k/parsers/__init__.py b/aiida_cp2k/parsers/__init__.py index bff0e47c..0e739434 100644 --- a/aiida_cp2k/parsers/__init__.py +++ b/aiida_cp2k/parsers/__init__.py @@ -6,7 +6,6 @@ # For further information on the license, see the LICENSE.txt file. # ############################################################################### """AiiDA-CP2K output parser.""" -from __future__ import absolute_import import io import os diff --git a/aiida_cp2k/utils/__init__.py b/aiida_cp2k/utils/__init__.py index 66efb34b..54e2d15b 100644 --- a/aiida_cp2k/utils/__init__.py +++ b/aiida_cp2k/utils/__init__.py @@ -6,7 +6,6 @@ # For further information on the license, see the LICENSE.txt file. # ############################################################################### """AiiDA-CP2K utils""" -from __future__ import absolute_import from .input_generator import Cp2kInput from .parser import parse_cp2k_output diff --git a/aiida_cp2k/utils/input_generator.py b/aiida_cp2k/utils/input_generator.py index 4ff35218..09e181e5 100644 --- a/aiida_cp2k/utils/input_generator.py +++ b/aiida_cp2k/utils/input_generator.py @@ -7,16 +7,9 @@ ############################################################################### """AiiDA-CP2K input generator""" -from __future__ import absolute_import -from __future__ import division - from copy import deepcopy -import six -if six.PY2: - from collections import Mapping, Sequence # pylint: disable=import-error, no-name-in-module -else: - from collections.abc import Mapping, Sequence # pylint: disable=import-error, no-name-in-module +from collections.abc import Mapping, Sequence class Cp2kInput: # pylint: disable=old-style-class @@ -55,7 +48,7 @@ def add_keyword(self, kwpath, value, override=True, conflicting_keys=None): added. """ - if isinstance(kwpath, six.string_types): + if isinstance(kwpath, str): kwpath = kwpath.split("/") Cp2kInput._add_keyword(kwpath, value, self._params, ovrd=override, cfct=conflicting_keys) @@ -89,7 +82,7 @@ def _add_keyword(kwpath, value, params, ovrd, cfct): Cp2kInput._add_keyword(kwpath[1:], value, params[kwpath[0]], ovrd, cfct) # if it is a list, loop over its elements - elif isinstance(params[kwpath[0]], Sequence) and not isinstance(params[kwpath[0]], six.string_types): + elif isinstance(params[kwpath[0]], Sequence) and not isinstance(params[kwpath[0]], str): for element in params[kwpath[0]]: Cp2kInput._add_keyword(kwpath[1:], value, element, ovrd, cfct) @@ -151,7 +144,7 @@ def _render_section(output, params, indent=0): Cp2kInput._render_section(output, val, indent + 3) output.append('{}&END {}'.format(' ' * indent, key)) - elif isinstance(val, Sequence) and not isinstance(val, six.string_types): + elif isinstance(val, Sequence) and not isinstance(val, str): for listitem in val: Cp2kInput._render_section(output, {key: listitem}, indent) diff --git a/aiida_cp2k/utils/parser.py b/aiida_cp2k/utils/parser.py index 4b4bee46..8ff8843d 100644 --- a/aiida_cp2k/utils/parser.py +++ b/aiida_cp2k/utils/parser.py @@ -7,9 +7,6 @@ ############################################################################### """AiiDA-CP2K input plugin.""" -from __future__ import absolute_import -from __future__ import division - import re import math diff --git a/aiida_cp2k/utils/workchains.py b/aiida_cp2k/utils/workchains.py index eb1ef54a..a41b6c91 100644 --- a/aiida_cp2k/utils/workchains.py +++ b/aiida_cp2k/utils/workchains.py @@ -7,7 +7,6 @@ ############################################################################### """AiiDA-CP2K utilities for workchains""" -from __future__ import absolute_import from aiida.engine import calcfunction from aiida.orm import Dict, StructureData diff --git a/aiida_cp2k/workchains/aiida_base_restart.py b/aiida_cp2k/workchains/aiida_base_restart.py index 65ec3c66..99840eba 100644 --- a/aiida_cp2k/workchains/aiida_base_restart.py +++ b/aiida_cp2k/workchains/aiida_base_restart.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # pylint: disable=inconsistent-return-statements,no-member """Base implementation of `WorkChain` class that implements a simple automated restart mechanism for calculations.""" -from __future__ import absolute_import from collections import namedtuple diff --git a/aiida_cp2k/workchains/base.py b/aiida_cp2k/workchains/base.py index c42d356f..ed5d3641 100644 --- a/aiida_cp2k/workchains/base.py +++ b/aiida_cp2k/workchains/base.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- """Base workchain to run a CP2K calculation""" -from __future__ import absolute_import from aiida.common import AttributeDict from aiida.engine import while_ diff --git a/conftest.py b/conftest.py index a4d01cd7..b78b81d9 100644 --- a/conftest.py +++ b/conftest.py @@ -1,7 +1,6 @@ """ For pytest initialise a test database and profile """ -from __future__ import absolute_import import pytest pytest_plugins = ['aiida.manage.tests.pytest_fixtures'] # pylint: disable=invalid-name diff --git a/examples/single_calculations/example_dft.py b/examples/single_calculations/example_dft.py index 2d68aa98..e42a1d36 100644 --- a/examples/single_calculations/example_dft.py +++ b/examples/single_calculations/example_dft.py @@ -8,9 +8,6 @@ ############################################################################### """Run simple DFT calculation.""" -from __future__ import print_function -from __future__ import absolute_import - import os import sys import click diff --git a/examples/single_calculations/example_dft_atomic_kinds.py b/examples/single_calculations/example_dft_atomic_kinds.py index deb9d954..e0d578d8 100644 --- a/examples/single_calculations/example_dft_atomic_kinds.py +++ b/examples/single_calculations/example_dft_atomic_kinds.py @@ -8,9 +8,6 @@ ############################################################################### """Run DFT calculation with different atomic kinds.""" -from __future__ import print_function -from __future__ import absolute_import - import os import sys import click diff --git a/examples/single_calculations/example_failure.py b/examples/single_calculations/example_failure.py index 54e1b012..da308ab9 100644 --- a/examples/single_calculations/example_failure.py +++ b/examples/single_calculations/example_failure.py @@ -7,8 +7,6 @@ # For further information on the license, see the LICENSE.txt file. # ############################################################################### """Run failing calculation.""" -from __future__ import print_function -from __future__ import absolute_import import sys import click diff --git a/examples/single_calculations/example_geopt.py b/examples/single_calculations/example_geopt.py index 6d805d36..85330c98 100644 --- a/examples/single_calculations/example_geopt.py +++ b/examples/single_calculations/example_geopt.py @@ -7,8 +7,6 @@ # For further information on the license, see the LICENSE.txt file. # ############################################################################### """Run DFT geometry optimization.""" -from __future__ import print_function -from __future__ import absolute_import import os import sys diff --git a/examples/single_calculations/example_mm.py b/examples/single_calculations/example_mm.py index f6777ff7..24cea1d8 100644 --- a/examples/single_calculations/example_mm.py +++ b/examples/single_calculations/example_mm.py @@ -7,8 +7,6 @@ # For further information on the license, see the LICENSE.txt file. # ############################################################################### """Run molecular mechanics calculation.""" -from __future__ import print_function -from __future__ import absolute_import import os import sys diff --git a/examples/single_calculations/example_multiple_force_eval.py b/examples/single_calculations/example_multiple_force_eval.py index 8ae6a1c6..e7dc8840 100644 --- a/examples/single_calculations/example_multiple_force_eval.py +++ b/examples/single_calculations/example_multiple_force_eval.py @@ -8,9 +8,6 @@ ############################################################################### """Run DFT calculation with multiple force eval sections.""" -from __future__ import print_function -from __future__ import absolute_import - import os import sys import click diff --git a/examples/single_calculations/example_no_struct.py b/examples/single_calculations/example_no_struct.py index c9f42226..56280bd2 100644 --- a/examples/single_calculations/example_no_struct.py +++ b/examples/single_calculations/example_no_struct.py @@ -8,9 +8,6 @@ ############################################################################### """Run DFT calculation with structure specified in the input file.""" -from __future__ import print_function -from __future__ import absolute_import - import os import sys import click diff --git a/examples/single_calculations/example_precision.py b/examples/single_calculations/example_precision.py index e7924964..db084500 100644 --- a/examples/single_calculations/example_precision.py +++ b/examples/single_calculations/example_precision.py @@ -7,8 +7,6 @@ # For further information on the license, see the LICENSE.txt file. # ############################################################################### """Test structure roundtrip precision ase->aiida->cp2k->aiida->ase.""" -from __future__ import print_function -from __future__ import absolute_import import os import sys diff --git a/examples/single_calculations/example_restart.py b/examples/single_calculations/example_restart.py index 9ab33d6a..a0fc2cca 100644 --- a/examples/single_calculations/example_restart.py +++ b/examples/single_calculations/example_restart.py @@ -7,8 +7,6 @@ # For further information on the license, see the LICENSE.txt file. # ############################################################################### """Test CP2K restart.""" -from __future__ import print_function -from __future__ import absolute_import import os import re diff --git a/examples/single_calculations/example_structure_through_file.py b/examples/single_calculations/example_structure_through_file.py index bc25a801..5e1b13e0 100644 --- a/examples/single_calculations/example_structure_through_file.py +++ b/examples/single_calculations/example_structure_through_file.py @@ -8,13 +8,9 @@ ############################################################################### """Run simple DFT calculation""" -from __future__ import print_function -from __future__ import absolute_import - import os import sys import click -from six.moves import range import ase.io diff --git a/examples/single_calculations/fixme_bands.py b/examples/single_calculations/fixme_bands.py index c54f7845..5347993f 100644 --- a/examples/single_calculations/fixme_bands.py +++ b/examples/single_calculations/fixme_bands.py @@ -8,9 +8,6 @@ ############################################################################### """Run simple Band Structure calculation""" -from __future__ import print_function -from __future__ import absolute_import - import os import sys import click diff --git a/examples/workchains/example_base.py b/examples/workchains/example_base.py index 4d120c99..7fc79348 100644 --- a/examples/workchains/example_base.py +++ b/examples/workchains/example_base.py @@ -8,9 +8,6 @@ ############################################################################### """Run simple DFT calculation through a workchain.""" -from __future__ import print_function -from __future__ import absolute_import - import os import sys import ase.io diff --git a/setup.json b/setup.json index 5fac0baa..4a589c53 100644 --- a/setup.json +++ b/setup.json @@ -7,15 +7,12 @@ "License :: OSI Approved :: MIT License", "Operating System :: POSIX :: Linux", "Operating System :: MacOS :: MacOS X", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7" + "Programming Language :: Python :: 3" ], "description": "The official AiiDA plugin for CP2K.", "install_requires": [ - "aiida-core>=1.0.0,<1.1.0", - "ase==3.17.0; python_version<'3.0'", - "ase; python_version>='3.5'", + "aiida-core>=1.1.0,<2.0.0", + "ase", "ruamel.yaml>=0.16.5" ], "entry_points": { @@ -31,25 +28,24 @@ ] }, "extras_require": { - "test": [ - "pgtest==1.2.0", - "pytest>=4.4,<5.0.0", - "pytest-cov>=2.6.1,<3.0.0", - "coverage" - ], - "pre-commit":[ - "pre-commit==1.18.3", - "yapf==0.28.0", - "prospector==1.1.7", - "pylint==1.9.4; python_version<'3.0'", - "pylint==2.3.1; python_version>='3.0'" - ], - "docs": [ - "sphinx", - "sphinx-rtd-theme", - "sphinxcontrib-contentui", - "sphinxcontrib-details-directive; python_version>='3.0'" - ] + "test": [ + "pgtest==1.2.0", + "pytest>=4.4,<5.0.0", + "pytest-cov>=2.6.1,<3.0.0", + "coverage" + ], + "pre-commit":[ + "pre-commit==1.18.3", + "yapf==0.28.0", + "prospector==1.1.7", + "pylint==2.3.1" + ], + "docs": [ + "sphinx", + "sphinx-rtd-theme", + "sphinxcontrib-contentui", + "sphinxcontrib-details-directive" + ] }, "license": "MIT License", "name": "aiida_cp2k", diff --git a/setup.py b/setup.py index 9b2905e8..09bd41bd 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,6 @@ # For further information on the license, see the LICENSE.txt file. # ############################################################################### """Setting up CP2K plugin for AiiDA""" -from __future__ import absolute_import import json diff --git a/test/test_input_generator.py b/test/test_input_generator.py index 52ab8740..1e793a75 100644 --- a/test/test_input_generator.py +++ b/test/test_input_generator.py @@ -7,8 +7,6 @@ ############################################################################### """Test Cp2k input generator""" -from __future__ import absolute_import - import pytest from aiida_cp2k.utils import Cp2kInput diff --git a/test/test_version_agreement.py b/test/test_version_agreement.py index b706b327..bca1846e 100644 --- a/test/test_version_agreement.py +++ b/test/test_version_agreement.py @@ -6,8 +6,6 @@ # For further information on the license, see the LICENSE.txt file. # ############################################################################### """Check versions""" -from __future__ import print_function -from __future__ import absolute_import import sys import json From 4d33411633c1844eb55d55861cfff9897f16277b Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Wed, 1 Apr 2020 11:03:04 +0200 Subject: [PATCH 2/6] Change docker base image to aiida-core. (#96) This PR replaces the base docker image with `aiida-core:latest`. In addition, it fixes one test where the running time was too short to produce an output structure. A clear error message was added to that case to allow quickly find out the origin of the problem. --- .coveragerc | 2 ++ .docker/my_init.d/add-codes.sh | 2 +- .docker/opt/add-codes.sh | 1 + .travis.yml | 3 +-- Dockerfile | 10 ++-------- examples/single_calculations/example_restart.py | 8 ++++++-- 6 files changed, 13 insertions(+), 13 deletions(-) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..08df5157 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,2 @@ +[run] +parallel=True diff --git a/.docker/my_init.d/add-codes.sh b/.docker/my_init.d/add-codes.sh index b7507a19..96ce5692 100755 --- a/.docker/my_init.d/add-codes.sh +++ b/.docker/my_init.d/add-codes.sh @@ -4,4 +4,4 @@ set -em su -c /opt/add-codes.sh aiida # Make /opt/aiida-cp2k folder editable for the $SYSTEM_USER. -chown -R aiida:aiida /opt/aiida-cp2k/ +chown -R ${SYSTEM_USER}:${SYSTEM_USER} /opt/aiida-cp2k/ diff --git a/.docker/opt/add-codes.sh b/.docker/opt/add-codes.sh index 1fb08faa..8b6e4352 100755 --- a/.docker/opt/add-codes.sh +++ b/.docker/opt/add-codes.sh @@ -6,4 +6,5 @@ set -x # Environment export SHELL=/bin/bash +# Install cp2k code. verdi code show cp2k@localhost || verdi code setup --config /opt/aiida-cp2k/.docker/cp2k-code.yml --non-interactive diff --git a/.travis.yml b/.travis.yml index ca5e87c9..69cb189b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,10 +7,9 @@ before_install: - python .ci/check_travis_tag.py - docker build -t aiida_cp2k_test . - export DOCKERID=`docker run -d aiida_cp2k_test` - - sleep 30 # wait until the container is launched script: - - "echo \"Docker ID: $DOCKERID\"" + - docker exec --tty $DOCKERID wait-for-services - docker exec -it --user aiida $DOCKERID /bin/bash -l -c 'cd /opt/aiida-cp2k/ && pre-commit run --all-files || ( git status --short ; git diff ; exit 1 )' - docker exec -it --user aiida $DOCKERID /bin/bash -l -c 'cd /opt/aiida-cp2k/ && py.test --cov aiida_cp2k --cov-append .' - docker exec -it --user aiida $DOCKERID /bin/bash -l -c 'cd /opt/aiida-cp2k/docs && make' diff --git a/Dockerfile b/Dockerfile index f00788bb..3ae5b2e1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,11 +5,7 @@ # For further information on the license, see the LICENSE.txt file. # ############################################################################### -FROM aiidateam/aiida-docker-stack - -# Set HOME and PATH variables. -ENV HOME="/home/aiida" -ENV PATH="${HOME}/.local/bin:${PATH}" +FROM aiidateam/aiida-core:latest # To prevent the container to exit prematurely. ENV KILL_ALL_RPOCESSES_TIMEOUT=50 @@ -28,6 +24,4 @@ RUN pip install coveralls # Install the cp2k code. COPY .docker/opt/add-codes.sh /opt/ -COPY .docker/my_init.d/add-codes.sh /etc/my_init.d/40_add-codes.sh - -RUN chown -R aiida:aiida ${HOME} +COPY .docker/my_init.d/add-codes.sh /etc/my_init.d/50_add-codes.sh diff --git a/examples/single_calculations/example_restart.py b/examples/single_calculations/example_restart.py index a0fc2cca..df922fa4 100644 --- a/examples/single_calculations/example_restart.py +++ b/examples/single_calculations/example_restart.py @@ -42,7 +42,7 @@ def example_restart(cp2k_code): dict={ 'GLOBAL': { 'RUN_TYPE': 'GEO_OPT', - 'WALLTIME': '00:00:10', # too short + 'WALLTIME': '00:00:20', # too short }, 'MOTION': { 'GEO_OPT': { @@ -123,7 +123,11 @@ def example_restart(cp2k_code): # Check walltime exceeded. assert calc1['output_parameters']['exceeded_walltime'] is True assert calc1['output_parameters']['energy'] is not None - assert 'output_structure' in calc1 + if 'output_structure' not in calc1: + print("There is no 'output_structure' in the process outputs. " + "Most probably the calculation did not reach the first geometry optimization step.") + sys.exit(1) + print("OK, walltime exceeded as expected.") # ------------------------------------------------------------------------------ From dc00d64786fe212286b563ad96e9b2c5e7f23368 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Wed, 1 Apr 2020 22:05:24 +0200 Subject: [PATCH 3/6] Use BaseRestartWorkChain from aiida-core. (#97) * `Cp2kBaseWorkChain` is now based on `BaseRestartWorkChain` imported from aiida. * Added `resubmit_unconverged_geometry` handler to `Cp2kBaseWorkChain` * `add_restart_section` calfunction has been added to the `input_generator.py` * Add tests for `Cp2kBaseWorkChain` that involve execution of `resubmit_unconverged_geometry` handler --- aiida_cp2k/utils/__init__.py | 2 +- aiida_cp2k/utils/input_generator.py | 27 +- aiida_cp2k/workchains/aiida_base_restart.py | 370 ------------------ aiida_cp2k/workchains/base.py | 73 +++- .../workchains/example_base_failed_restart.py | 140 +++++++ ...xample_base.py => example_base_restart.py} | 16 + 6 files changed, 248 insertions(+), 380 deletions(-) delete mode 100644 aiida_cp2k/workchains/aiida_base_restart.py create mode 100644 examples/workchains/example_base_failed_restart.py rename examples/workchains/{example_base.py => example_base_restart.py} (87%) diff --git a/aiida_cp2k/utils/__init__.py b/aiida_cp2k/utils/__init__.py index 54e2d15b..e08ae652 100644 --- a/aiida_cp2k/utils/__init__.py +++ b/aiida_cp2k/utils/__init__.py @@ -7,7 +7,7 @@ ############################################################################### """AiiDA-CP2K utils""" -from .input_generator import Cp2kInput +from .input_generator import Cp2kInput, add_restart_sections from .parser import parse_cp2k_output from .parser import parse_cp2k_output_advanced from .parser import parse_cp2k_trajectory diff --git a/aiida_cp2k/utils/input_generator.py b/aiida_cp2k/utils/input_generator.py index 09e181e5..58355a61 100644 --- a/aiida_cp2k/utils/input_generator.py +++ b/aiida_cp2k/utils/input_generator.py @@ -5,12 +5,15 @@ # AiiDA-CP2K is hosted on GitHub at https://github.com/aiidateam/aiida-cp2k # # For further information on the license, see the LICENSE.txt file. # ############################################################################### -"""AiiDA-CP2K input generator""" +"""AiiDA-CP2K input generator.""" from copy import deepcopy - from collections.abc import Mapping, Sequence +from aiida.orm import Dict +from aiida.engine import calcfunction +from .workchains import merge_dict + class Cp2kInput: # pylint: disable=old-style-class """Transforms dictionary into CP2K input""" @@ -154,3 +157,23 @@ def _render_section(output, params, indent=0): else: output.append('{}{} {}'.format(' ' * indent, key, val)) + + +@calcfunction +def add_restart_sections(input_dict): + """Add restart section to the input dictionary.""" + + params = input_dict.get_dict() + restart_wfn_dict = { + 'FORCE_EVAL': { + 'DFT': { + 'RESTART_FILE_NAME': './parent_calc/aiida-RESTART.wfn', + 'SCF': { + 'SCF_GUESS': 'RESTART', + }, + }, + }, + } + merge_dict(params, restart_wfn_dict) + params['EXT_RESTART'] = {'RESTART_FILE_NAME': './parent_calc/aiida-1.restart'} + return Dict(dict=params) diff --git a/aiida_cp2k/workchains/aiida_base_restart.py b/aiida_cp2k/workchains/aiida_base_restart.py deleted file mode 100644 index 99840eba..00000000 --- a/aiida_cp2k/workchains/aiida_base_restart.py +++ /dev/null @@ -1,370 +0,0 @@ -# -*- coding: utf-8 -*- -# pylint: disable=inconsistent-return-statements,no-member -"""Base implementation of `WorkChain` class that implements a simple automated restart mechanism for calculations.""" - -from collections import namedtuple - -from aiida import orm -from aiida.orm import Dict -from aiida.common import exceptions, AttributeDict, AiidaException -from aiida.common.lang import override -from aiida.engine import CalcJob, WorkChain, ToContext, append_, ExitCode -from aiida.plugins.entry_point import get_entry_point_names, load_entry_point - - -class UnexpectedCalculationFailure(AiidaException): - """Raised when a calculation job has failed for an unexpected or unrecognized reason.""" - - -ErrorHandlerReport = namedtuple('ErrorHandlerReport', 'is_handled do_break exit_code') -ErrorHandlerReport.__new__.__defaults__ = (False, False, ExitCode()) -""" -A namedtuple to define an error handler report for a :class:`~aiida.engine.processes.workchains.workchain.WorkChain`. - -This namedtuple should be returned by an error handling method of a workchain instance if -the condition of the error handling was met by the failure mode of the calculation. -If the error was appriopriately handled, the 'is_handled' field should be set to `True`, -and `False` otherwise. If no further error handling should be performed after this method -the 'do_break' field should be set to `True` - -:param is_handled: boolean, set to `True` when an error was handled, default is `False` -:param do_break: boolean, set to `True` if no further error handling should be performed, default is `False` -:param exit_code: an instance of the :class:`~aiida.engine.processes.exit_code.ExitCode` tuple -""" - - -def prepare_process_inputs(process, inputs): - """Prepare the inputs for submission for the given process, according to its spec. - - That is to say that when an input is found in the inputs that corresponds to an input port in the spec of the - process that expects a `Dict`, yet the value in the inputs is a plain dictionary, the value will be wrapped in by - the `Dict` class to create a valid input. - - :param process: sub class of `Process` for which to prepare the inputs dictionary - :param inputs: a dictionary of inputs intended for submission of the process - :return: a dictionary with all bare dictionaries wrapped in `Dict` if dictated by the process spec - """ - prepared_inputs = wrap_bare_dict_inputs(process.spec().inputs, inputs) - return AttributeDict(prepared_inputs) - - -def wrap_bare_dict_inputs(port_namespace, inputs): - """Wrap bare dictionaries in `inputs` in a `Dict` node if dictated by the corresponding port in given namespace. - - :param port_namespace: a `PortNamespace` - :param inputs: a dictionary of inputs intended for submission of the process - :return: a dictionary with all bare dictionaries wrapped in `Dict` if dictated by the port namespace - """ - from aiida.engine.processes import PortNamespace - - wrapped = {} - - for key, value in inputs.items(): - - if key not in port_namespace: - wrapped[key] = value - continue - - port = port_namespace[key] - - if isinstance(port, PortNamespace): - wrapped[key] = wrap_bare_dict_inputs(port, value) - elif port.valid_type == Dict and isinstance(value, dict): - wrapped[key] = Dict(dict=value) - else: - wrapped[key] = value - - return wrapped - - -class BaseRestartWorkChain(WorkChain): - """Base restart work chain - - This work chain serves as the starting point for more complex work chains that will be designed to run a calculation - that might need multiple restarts to come to a successful end. These restarts may be necessary because a single - calculation run is not sufficient to achieve a fully converged result, or certain errors maybe encountered which - are recoverable. - - This work chain implements the most basic functionality to achieve this goal. It will launch calculations, - restarting until it is completed successfully or the maximum number of iterations is reached. It can recover from - errors through error handlers that can be attached dynamically through the `register_error_handler` decorator. - - The idea is to sub class this work chain and leverage the generic error handling that is implemented in the few - outline methods. The minimally required outline would look something like the following:: - - cls.setup - while_(cls.should_run_calculation)( - cls.run_calculation, - cls.inspect_calculation, - ) - - Each of these methods can of course be overriden but they should be general enough to fit most calculation cycles. - The `run_calculation` method will take the inputs for the calculation process from the context under the key - `inputs`. The user should therefore make sure that before the `run_calculation` method is called, that the to be - used inputs are stored under `self.ctx.inputs`. One can update the inputs based on the results from a prior - calculation by calling an outline method just before the `run_calculation` step, for example:: - - cls.setup - while_(cls.should_run_calculation)( - cls.prepare_calculation, - cls.run_calculation, - cls.inspect_calculation, - ) - - Where in the `prepare_calculation` method, the inputs dictionary at `self.ctx.inputs` is updated before the next - calculation will be run with those inputs. - - The `_calculation_class` attribute should be set to the `CalcJob` class that should be run in the loop. - """ - _verbose = False - _calculation_class = None - _error_handler_entry_point = None - - def __init__(self, *args, **kwargs): - super(BaseRestartWorkChain, self).__init__(*args, **kwargs) - - if self._calculation_class is None or not issubclass(self._calculation_class, CalcJob): - raise ValueError('no valid CalcJob class defined for `_calculation_class` attribute') - - self._load_error_handlers() - - @override - def load_instance_state(self, saved_state, load_context): - super(BaseRestartWorkChain, self).load_instance_state(saved_state, load_context) - self._load_error_handlers() - - def _load_error_handlers(self): - """If an error handler entry point is defined, load them. If the plugin cannot be loaded log it and pass.""" - if self._error_handler_entry_point is not None: - for entry_point_name in get_entry_point_names(self._error_handler_entry_point): - try: - load_entry_point(self._error_handler_entry_point, entry_point_name) - self.logger.info("loaded the '%s' entry point for the '%s' error handlers category", - entry_point_name, self._error_handler_entry_point) - except exceptions.EntryPointError as exception: - self.logger.warning("failed to load the '%s' entry point for the '%s' error handlers: %s", - entry_point_name, self._error_handler_entry_point, exception) - - @classmethod - def define(cls, spec): - # yapf: disable - # pylint: disable=bad-continuation - super(BaseRestartWorkChain, cls).define(spec) - spec.input('max_iterations', valid_type=orm.Int, default=lambda: orm.Int(5), - help='Maximum number of iterations the work chain will restart the calculation to finish successfully.') - spec.input('clean_workdir', valid_type=orm.Bool, default=lambda: orm.Bool(False), - help='If `True`, work directories of all called calculation will be cleaned at the end of execution.') - spec.exit_code(101, 'ERROR_MAXIMUM_ITERATIONS_EXCEEDED', - message='The maximum number of iterations was exceeded.') - spec.exit_code(102, 'ERROR_SECOND_CONSECUTIVE_UNHANDLED_FAILURE', - message='The calculation failed for an unknown reason, twice in a row.') - - def setup(self): - """Initialize context variables that are used during the logical flow of the `BaseRestartWorkChain`.""" - self.ctx.calc_name = self._calculation_class.__name__ - self.ctx.unexpected_failure = False - self.ctx.restart_calc = None - self.ctx.is_finished = False - self.ctx.iteration = 0 - - def should_run_calculation(self): - """Return whether a new calculation should be run. - - This is the case as long as the last calculation has not finished successfully and the maximum number of - restarts has not yet been exceeded. - """ - return not self.ctx.is_finished and self.ctx.iteration < self.inputs.max_iterations.value - - def run_calculation(self): - """Run the next calculation, taking the input dictionary from the context at `self.ctx.inputs`.""" - self.ctx.iteration += 1 - - try: - unwrapped_inputs = self.ctx.inputs - except AttributeError: - raise AttributeError('no calculation input dictionary was defined in `self.ctx.inputs`') - - # Set the `CALL` link label - unwrapped_inputs['metadata']['call_link_label'] = 'iteration_{:02d}'.format(self.ctx.iteration) - - inputs = prepare_process_inputs(self._calculation_class, unwrapped_inputs) - calculation = self.submit(self._calculation_class, **inputs) - - # Add a new empty list to the `errors_handled` extra. If any errors handled registered through the - # `register_error_handler` decorator return an `ErrorHandlerReport`, their name will be appended to that list. - errors_handled = self.node.get_extra('errors_handled', []) - errors_handled.append([]) - self.node.set_extra('errors_handled', errors_handled) - - self.report('launching {}<{}> iteration #{}'.format(self.ctx.calc_name, calculation.pk, self.ctx.iteration)) - - return ToContext(calculations=append_(calculation)) - - def inspect_calculation(self): - """Analyse the results of the previous calculation and call the error handlers when necessary.""" - calculation = self.ctx.calculations[self.ctx.iteration - 1] - - # Done: successful completion of last calculation - if calculation.is_finished_ok: - - # Perform an optional sanity check. If it returns an `ExitCode` this means an unrecoverable situation was - # detected and the work chain should be aborted. If it returns `False`, the sanity check detected a problem - # but has handled the problem and we should restart the cycle. - handler = self._handle_calculation_sanity_checks(calculation) # pylint: disable=assignment-from-no-return - - if isinstance(handler, ErrorHandlerReport) and handler.exit_code.status != 0: - # Sanity check returned a handler with an exit code that is non-zero, so we abort - self.report('{}<{}> finished successfully, but sanity check detected unrecoverable problem'.format( - self.ctx.calc_name, calculation.pk)) - return handler.exit_code - - if isinstance(handler, ErrorHandlerReport): - # Reset the `unexpected_failure` since we are restarting the calculation loop - self.ctx.unexpected_failure = False - self.report('{}<{}> finished successfully, but sanity check failed, restarting'.format( - self.ctx.calc_name, calculation.pk)) - return - - self.report('{}<{}> completed successfully'.format(self.ctx.calc_name, calculation.pk)) - self.ctx.restart_calc = calculation - self.ctx.is_finished = True - return - - # Unexpected: calculation was killed or an exception occurred, trigger unexpected failure handling - if calculation.is_excepted or calculation.is_killed: - return self._handle_unexpected_failure(calculation) - - # Failed: here the calculation is `Finished` but has a non-zero exit status, initiate the error handling - try: - exit_code = self._handle_calculation_failure(calculation) - except UnexpectedCalculationFailure as exception: - exit_code = self._handle_unexpected_failure(calculation, exception) - - return exit_code - - def results(self): - """Attach the outputs specified in the output specification from the last completed calculation.""" - calculation = self.ctx.calculations[self.ctx.iteration - 1] - - if calculation.is_failed and self.ctx.iteration >= self.inputs.max_iterations.value: - # Abort: exceeded maximum number of retries - self.report('reached the maximum number of iterations {}: last ran {}<{}>'.format( - self.inputs.max_iterations.value, self.ctx.calc_name, calculation.pk)) - return self.exit_codes.ERROR_MAXIMUM_ITERATIONS_EXCEEDED - - self.report('work chain completed after {} iterations'.format(self.ctx.iteration)) - - for name, port in self.spec().outputs.items(): - - try: - node = calculation.get_outgoing(link_label_filter=name).one().node - except ValueError: - if port.required: - self.report("required output '{}' was not an output of {}<{}>".format( - name, self.ctx.calc_name, calculation.pk)) - else: - self.out(name, node) - if self._verbose: - self.report("attaching the node {}<{}> as '{}'".format(node.__class__.__name__, node.pk, name)) - - def on_terminated(self): - """Clean the working directories of all child calculations if `clean_workdir=True` in the inputs.""" - super(BaseRestartWorkChain, self).on_terminated() - - if self.inputs.clean_workdir.value is False: - self.report('remote folders will not be cleaned') - return - - cleaned_calcs = [] - - for called_descendant in self.node.called_descendants: - if isinstance(called_descendant, orm.CalcJobNode): - try: - called_descendant.outputs.remote_folder._clean() # pylint: disable=protected-access - cleaned_calcs.append(str(called_descendant.pk)) - except (IOError, OSError, KeyError): - pass - - if cleaned_calcs: - self.report('cleaned remote folders of calculations: {}'.format(' '.join(cleaned_calcs))) - - def _handle_calculation_sanity_checks(self, calculation): - """Perform a sanity check of a calculation that finished ok. - - Calculations that were marked as successful by the parser may still have produced outputs that do not make sense - but were not detected by the code and so were not highlighted as warnings or errors. The consistency of the - outputs can be checked here. If an unrecoverable problem is found, the function should return the appropriate - exit code to abort the work chain. If the probem can be fixed with a restart calculation, this function should - adapt the inputs as an error handler would and return `False`. This will signal to the work chain that a new - calculation should be started. If `None` is returned, the work chain assumes that the outputs produced by the - calculation are good and nothing will be done. - - :param calculation: the calculation whose outputs should be checked for consistency - :return: `ErrorHandlerReport` if a new calculation should be launched or abort if it includes an exit code - """ - - def _handle_calculation_failure(self, calculation): - """Call the attached error handlers if any to attempt to correct the cause of the calculation failure. - - The registered error handlers will be called in order based on their priority until a handler returns a report - that instructs to break. If the last executed error handler defines an exit code, that will be returned to - instruct the work chain to abort. Otherwise the work chain will continue the cycle. - - :param calculation: the calculation that finished with a non-zero exit status - :return: `ExitCode` if the work chain is to be aborted - :raises `UnexpectedCalculationFailure`: if no error handlers were registered or no errors were handled. - """ - is_handled = False - handler_report = None - - if not hasattr(self, '_error_handlers') or not self._error_handlers: - raise UnexpectedCalculationFailure('no calculation error handlers were registered') - - # Sort the handlers with a priority defined, based on their priority in reverse order - handlers = [handler for handler in self._error_handlers if handler.priority] - handlers = sorted(handlers, key=lambda x: x.priority, reverse=True) - - for handler in handlers: - - handler_report = handler.method(self, calculation) - - # If at least one error is handled, we consider the calculation failure handled. - if handler_report and handler_report.is_handled: - self.ctx.unexpected_failure = False - is_handled = True - - # After certain error handlers, we may want to skip all other error handling - if handler_report and handler_report.do_break: - break - - # If none of the executed error handlers reported that they handled an error, the failure reason is unknown - if not is_handled: - raise UnexpectedCalculationFailure('calculation failure was not handled') - - # The last called error handler may not necessarily have returned a handler report - if handler_report: - return handler_report.exit_code - - return - - def _handle_unexpected_failure(self, calculation, exception=None): - """Handle an unexpected failure. - - This occurs when a calculation excepted, was killed or finished with a non-zero exit status but no errors were - handled. If this is the second consecutive unexpected failure the work chain is aborted. - - :param calculation: the calculation that failed in an unexpected way - :param exception: optional exception or error message to log to the report - :return: `ExitCode` if this is the second consecutive unexpected failure - """ - if exception: - self.report('{}'.format(exception)) - - if self.ctx.unexpected_failure: - self.report('failure of {}<{}> could not be handled for the second consecutive time'.format( - self.ctx.calc_name, calculation.pk)) - return self.exit_codes.ERROR_SECOND_CONSECUTIVE_UNHANDLED_FAILURE - - self.ctx.unexpected_failure = True - self.report('failure of {}<{}> could not be handled, restarting once more'.format( - self.ctx.calc_name, calculation.pk)) diff --git a/aiida_cp2k/workchains/base.py b/aiida_cp2k/workchains/base.py index ed5d3641..86ea430c 100644 --- a/aiida_cp2k/workchains/base.py +++ b/aiida_cp2k/workchains/base.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- -"""Base workchain to run a CP2K calculation""" +"""Base work chain to run a CP2K calculation.""" from aiida.common import AttributeDict -from aiida.engine import while_ +from aiida.engine import BaseRestartWorkChain, ExitCode, ProcessHandlerReport, process_handler, while_ from aiida.plugins import CalculationFactory -from aiida_cp2k.workchains.aiida_base_restart import BaseRestartWorkChain +from ..utils import add_restart_sections Cp2kCalculation = CalculationFactory('cp2k') # pylint: disable=invalid-name @@ -13,7 +13,7 @@ class Cp2kBaseWorkChain(BaseRestartWorkChain): """Workchain to run a CP2K calculation with automated error handling and restarts.""" - _calculation_class = Cp2kCalculation + _process_class = Cp2kCalculation @classmethod def define(cls, spec): @@ -23,9 +23,9 @@ def define(cls, spec): spec.outline( cls.setup, - while_(cls.should_run_calculation)( - cls.run_calculation, - cls.inspect_calculation, + while_(cls.should_run_process)( + cls.run_process, + cls.inspect_process, ), cls.results, ) @@ -38,5 +38,64 @@ def setup(self): This `self.ctx.inputs` dictionary will be used by the `BaseRestartWorkChain` to submit the calculations in the internal loop. """ + super(Cp2kBaseWorkChain, self).setup() self.ctx.inputs = AttributeDict(self.exposed_inputs(Cp2kCalculation, 'cp2k')) + + + @process_handler(priority=400, enabled=False) + def resubmit_unconverged_geometry(self, calc): + """Resubmit a calculation it is not converged, but can be recovered.""" + + self.report("Checking the geometry convergence.") + + content_string = calc.outputs.retrieved.get_object_content(calc.get_attribute('output_filename')) + + time_not_exceeded = "PROGRAM ENDED AT" + time_exceeded = "exceeded requested execution time" + one_step_done = "Max. gradient =" + self.ctx.inputs.parent_calc_folder = calc.outputs.remote_folder + params = self.ctx.inputs.parameters + + # If the problem is recoverable then do restart + if (time_not_exceeded not in content_string or time_exceeded in content_string) and one_step_done in content_string: # pylint: disable=line-too-long + try: + # Firts check if all the restart keys are present in the input dictionary + wf_rest_fname_pointer = params['FORCE_EVAL']['DFT']['RESTART_FILE_NAME'] + scf_guess_pointer = params['FORCE_EVAL']['DFT']['SCF']['SCF_GUESS'] + restart_fname_pointer = params['EXT_RESTART']['RESTART_FILE_NAME'] + + # Also check if they all have the right value + if not (wf_rest_fname_pointer == './parent_calc/aiida-RESTART.wfn' and + scf_guess_pointer == 'RESTART' and + restart_fname_pointer == './parent_calc/aiida-1.restart'): + + # If some values are incorrect add them to the input dictionary + params = add_restart_sections(params) + + # If not all the restart keys are present, adding them to the input dictionary + except KeyError: + params = add_restart_sections(params) + + # Might be able to solve the problem + self.ctx.inputs.parameters = params # params (new or old ones) that for sure + # include the necessary restart key-value pairs + self.report( + "The CP2K calculation wasn't completed. The restart of the calculation might be able to " + "fix the problem.") + return ProcessHandlerReport(False) + + # If the problem is not recoverable + if (time_not_exceeded not in content_string or + time_exceeded in content_string) and one_step_done not in content_string: + + self.report("It seems that the restart of CP2K calculation wouldn't be able to fix the problem as the " + "geometry optimization couldn't complete a single step. Sending a signal to stop the Base " + "work chain.") + + # Signaling to the base work chain that the problem could not be recovered. + return ProcessHandlerReport(True, ExitCode(1)) + + self.report("The geometry seem to be converged.") + # If everything is alright + return None diff --git a/examples/workchains/example_base_failed_restart.py b/examples/workchains/example_base_failed_restart.py new file mode 100644 index 00000000..51a58cf8 --- /dev/null +++ b/examples/workchains/example_base_failed_restart.py @@ -0,0 +1,140 @@ +# -*- coding: utf-8 -*- +# pylint: disable=invalid-name +############################################################################### +# Copyright (c), The AiiDA-CP2K authors. # +# SPDX-License-Identifier: MIT # +# AiiDA-CP2K is hosted on GitHub at https://github.com/aiidateam/aiida-cp2k # +# For further information on the license, see the LICENSE.txt file. # +############################################################################### +"""Run simple DFT calculation through a workchain.""" + +import os +import sys +import ase.io +import click + +from aiida.engine import run_get_node +from aiida.orm import (Code, Dict, SinglefileData, StructureData) +from aiida.common import NotExistent +from aiida.plugins import WorkflowFactory + +Cp2kBaseWorkChain = WorkflowFactory('cp2k.base') + + +def example_base(cp2k_code): + """Run simple DFT calculation through a workchain.""" + + thisdir = os.path.dirname(os.path.realpath(__file__)) + + print("Testing CP2K ENERGY on H2O (DFT) through a workchain...") + + # Basis set. + basis_file = SinglefileData(file=os.path.join(thisdir, "..", "files", "BASIS_MOLOPT")) + + # Pseudopotentials. + pseudo_file = SinglefileData(file=os.path.join(thisdir, "..", "files", "GTH_POTENTIALS")) + + # Structure. + structure = StructureData(ase=ase.io.read(os.path.join(thisdir, "..", "files", "h2o.xyz"))) + + # Parameters. + parameters = Dict( + dict={ + 'GLOBAL': { + 'RUN_TYPE': 'GEO_OPT', + 'WALLTIME': '00:00:05', # Can't even do one geo opt step. + }, + 'FORCE_EVAL': { + 'METHOD': 'Quickstep', + 'DFT': { + 'BASIS_SET_FILE_NAME': 'BASIS_MOLOPT', + 'POTENTIAL_FILE_NAME': 'GTH_POTENTIALS', + 'QS': { + 'EPS_DEFAULT': 1.0e-12, + 'WF_INTERPOLATION': 'ps', + 'EXTRAPOLATION_ORDER': 3, + }, + 'MGRID': { + 'NGRIDS': 4, + 'CUTOFF': 280, + 'REL_CUTOFF': 30, + }, + 'XC': { + 'XC_FUNCTIONAL': { + '_': 'LDA', + }, + }, + 'POISSON': { + 'PERIODIC': 'none', + 'PSOLVER': 'MT', + }, + 'SCF': { + 'PRINT': { + 'RESTART': { + '_': 'ON' + } + } + }, + }, + 'SUBSYS': { + 'KIND': [ + { + '_': 'O', + 'BASIS_SET': 'DZVP-MOLOPT-SR-GTH', + 'POTENTIAL': 'GTH-LDA-q6' + }, + { + '_': 'H', + 'BASIS_SET': 'DZVP-MOLOPT-SR-GTH', + 'POTENTIAL': 'GTH-LDA-q1' + }, + ], + }, + } + }) + + # Construct process builder. + builder = Cp2kBaseWorkChain.get_builder() + + # Switch on resubmit_unconverged_geometry disabled by default. + builder.handler_overrides = Dict(dict={'resubmit_unconverged_geometry': True}) + + # Input structure. + builder.cp2k.structure = structure + builder.cp2k.parameters = parameters + builder.cp2k.code = cp2k_code + builder.cp2k.file = { + 'basis': basis_file, + 'pseudo': pseudo_file, + } + builder.cp2k.metadata.options.resources = { + "num_machines": 1, + "num_mpiprocs_per_machine": 1, + } + builder.cp2k.metadata.options.max_wallclock_seconds = 1 * 3 * 60 + + print("Submitted calculation...") + _, process_node = run_get_node(builder) + + if process_node.exit_status == 1: + print("Work chain failure correctly recognized.") + else: + print("ERROR!") + print("Work chain failure was not recognized.") + sys.exit(3) + + +@click.command('cli') +@click.argument('codelabel') +def cli(codelabel): + """Click interface.""" + try: + code = Code.get_from_string(codelabel) + except NotExistent: + print("The code '{}' does not exist".format(codelabel)) + sys.exit(1) + example_base(code) + + +if __name__ == '__main__': + cli() # pylint: disable=no-value-for-parameter diff --git a/examples/workchains/example_base.py b/examples/workchains/example_base_restart.py similarity index 87% rename from examples/workchains/example_base.py rename to examples/workchains/example_base_restart.py index 7fc79348..085b1b09 100644 --- a/examples/workchains/example_base.py +++ b/examples/workchains/example_base_restart.py @@ -40,6 +40,10 @@ def example_base(cp2k_code): # Parameters. parameters = Dict( dict={ + 'GLOBAL': { + 'RUN_TYPE': 'GEO_OPT', + 'WALLTIME': '00:00:20', # too short + }, 'FORCE_EVAL': { 'METHOD': 'Quickstep', 'DFT': { @@ -64,6 +68,13 @@ def example_base(cp2k_code): 'PERIODIC': 'none', 'PSOLVER': 'MT', }, + 'SCF': { + 'PRINT': { + 'RESTART': { + '_': 'ON' + } + } + }, }, 'SUBSYS': { 'KIND': [ @@ -84,6 +95,11 @@ def example_base(cp2k_code): # Construct process builder. builder = Cp2kBaseWorkChain.get_builder() + + # Switch on resubmit_unconverged_geometry disabled by default. + builder.handler_overrides = Dict(dict={'resubmit_unconverged_geometry': True}) + + # Input structure. builder.cp2k.structure = structure builder.cp2k.parameters = parameters builder.cp2k.code = cp2k_code From ddd4075d8b3d9dfc16c789e321823f97b3f0fb53 Mon Sep 17 00:00:00 2001 From: Kristjan Eimre Date: Sun, 5 Apr 2020 19:02:18 +0200 Subject: [PATCH 4/6] Enable dynamic outputs. (#99) Enable dynamic outputs to allow custom parsers creating extra output nodes. For example, a StructureData for each converged NEB replica geometry. --- aiida_cp2k/calculations/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/aiida_cp2k/calculations/__init__.py b/aiida_cp2k/calculations/__init__.py index aa459101..b4590359 100644 --- a/aiida_cp2k/calculations/__init__.py +++ b/aiida_cp2k/calculations/__init__.py @@ -88,6 +88,8 @@ def define(cls, spec): spec.output('output_bands', valid_type=BandsData, required=False, help='optional band structure') spec.default_output_node = 'output_parameters' + spec.outputs.dynamic = True + def prepare_for_submission(self, folder): """Create the input files from the input nodes passed to this instance of the `CalcJob`. From 8f6635bec78ca46ae29fb9ec744b74c67154cda2 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Wed, 15 Apr 2020 15:11:17 +0200 Subject: [PATCH 5/6] Conftest: change executable name Replace executable name from 'cp2k.popt' to 'cp2k' which is more general. --- conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conftest.py b/conftest.py index b78b81d9..222170de 100644 --- a/conftest.py +++ b/conftest.py @@ -7,4 +7,4 @@ @pytest.fixture(scope='function') def cp2k_code(aiida_local_code_factory): # pylint: disable=unused-argument - return aiida_local_code_factory("cp2k", "cp2k.popt") + return aiida_local_code_factory("cp2k", "cp2k") From 6d30babc63727c53797da12ac8e237ccfde3e92a Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Fri, 17 Apr 2020 22:05:54 +0200 Subject: [PATCH 6/6] Prepare release 1.1.0 --- aiida_cp2k/__init__.py | 2 +- setup.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/aiida_cp2k/__init__.py b/aiida_cp2k/__init__.py index a8af0a72..36a88db5 100644 --- a/aiida_cp2k/__init__.py +++ b/aiida_cp2k/__init__.py @@ -7,6 +7,6 @@ ############################################################################### """AiiDA-CP2K plugins, parsers, workflows, etc ...""" -__version__ = "1.0.0" +__version__ = "1.1.0" # EOF diff --git a/setup.json b/setup.json index 4a589c53..c602c3b2 100644 --- a/setup.json +++ b/setup.json @@ -50,5 +50,5 @@ "license": "MIT License", "name": "aiida_cp2k", "url": "https://github.com/aiidateam/aiida-cp2k", - "version": "1.0.0" + "version": "1.1.0" }