Skip to content

Commit 961c4a7

Browse files
committed
handling of SCF not converged if walltime not exceeded
1 parent 400fa3d commit 961c4a7

File tree

7 files changed

+207
-1
lines changed

7 files changed

+207
-1
lines changed

aiida_cp2k/calculations/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ def define(cls, spec):
205205
"ERROR_OUT_OF_WALLTIME",
206206
message="The calculation stopped prematurely because it ran out of walltime.",
207207
)
208+
spec.exit_code(450, "ERROR_SCF_NOT_CONVERGED", message="SCF cycle did not converge for thegiven threshold.")
208209
spec.exit_code(
209210
500,
210211
"ERROR_GEOMETRY_CONVERGENCE_NOT_REACHED",

aiida_cp2k/parsers/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ def _parse_final_structure(self):
9898

9999
def _check_stdout_for_errors(self, output_string):
100100
"""This function checks the CP2K output file for some basic errors."""
101+
if "ABORT" in output_string and "SCF run NOT converged. To continue the calculation regardless" in output_string:
102+
return self.exit_codes.ERROR_SCF_NOT_CONVERGED
101103

102104
if "ABORT" in output_string:
103105
return self.exit_codes.ERROR_OUTPUT_CONTAINS_ABORT
@@ -107,6 +109,9 @@ def _check_stdout_for_errors(self, output_string):
107109

108110
if "PROGRAM STOPPED IN" not in output_string:
109111
return self.exit_codes.ERROR_OUTPUT_INCOMPLETE
112+
113+
if "SCF run NOT converged ***" in output_string:
114+
return self.exit_codes.ERROR_SCF_NOT_CONVERGED
110115

111116
if "MAXIMUM NUMBER OF OPTIMIZATION STEPS REACHED" in output_string:
112117
return self.exit_codes.ERROR_MAXIMUM_NUMBER_OPTIMIZATION_STEPS_REACHED

aiida_cp2k/utils/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
add_ext_restart_section,
1616
add_first_snapshot_in_reftraj_section,
1717
add_wfn_restart_section,
18+
add_ignore_convergence_failure,
1819
)
1920
from .parser import parse_cp2k_output, parse_cp2k_output_advanced, parse_cp2k_trajectory
2021
from .workchains import (
@@ -27,11 +28,13 @@
2728
merge_Dict,
2829
ot_has_small_bandgap,
2930
resize_unit_cell,
31+
get_last_convergence_value,
3032
)
3133

3234
__all__ = [
3335
"Cp2kInput",
3436
"add_ext_restart_section",
37+
"add_ignore_convergence_failure",
3538
"add_first_snapshot_in_reftraj_section",
3639
"add_wfn_restart_section",
3740
"parse_cp2k_output",
@@ -42,6 +45,7 @@
4245
"check_resize_unit_cell",
4346
"get_input_multiplicity",
4447
"get_kinds_section",
48+
"get_last_convergence_value",
4549
"merge_dict",
4650
"merge_Dict",
4751
"ot_has_small_bandgap",

aiida_cp2k/utils/input_generator.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,3 +230,9 @@ def add_first_snapshot_in_reftraj_section(input_dict, first_snapshot):
230230
params = input_dict.get_dict()
231231
params["MOTION"]["MD"]["REFTRAJ"]["FIRST_SNAPSHOT"] = first_snapshot
232232
return Dict(params)
233+
@calcfunction
234+
def add_ignore_convergence_failure(input_dict):
235+
"""Add IGNORE_CONVERGENCE_FAILURE for non converged SCF runs."""
236+
params = input_dict.get_dict()
237+
params["FORCE_EVAL"]["DFT"]["SCF"]["IGNORE_CONVERGENCE_FAILURE"] = ".TRUE."
238+
return Dict(params)

aiida_cp2k/utils/workchains.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
###############################################################################
77
"""AiiDA-CP2K utilities for workchains"""
88

9+
import re
910
from aiida.engine import calcfunction
1011
from aiida.orm import Dict
1112
from aiida.plugins import DataFactory
@@ -99,6 +100,32 @@ def ot_has_small_bandgap(cp2k_input, cp2k_output, bandgap_thr_ev):
99100
is_bandgap_small = min_bandgap_ev < bandgap_thr_ev
100101
return using_ot and is_bandgap_small
101102

103+
def get_last_convergence_value(input_string):
104+
"""
105+
Search for last "OT CG" and returns the SCF gradient.
106+
If no "OT CG", searches for last "DIIS/Diag" and returns the gradient.
107+
108+
Args:
109+
input_string (str): the cp2k output string.
110+
111+
Returns:
112+
float or None: the SCF gradient or None if not found.
113+
"""
114+
# Search all "OT CG" lines and gets the 6th column
115+
ot_cg_pattern = r"OT CG\s+\S+\s+\S+\s+\S+\s+\S+\s+([\d.E+-]+)"
116+
ot_cg_matches = re.findall(ot_cg_pattern, input_string)
117+
118+
if ot_cg_matches:
119+
return float(ot_cg_matches[-1]) # Last value found for "OT CG"
120+
121+
# Search for "DIIS/Diag" lines and returns the 5th column
122+
diis_diag_pattern = r"DIIS/Diag\.\s+\S+\s+\S+\s+\S+\s+([\d.E+-]+)"
123+
diis_diag_matches = re.findall(diis_diag_pattern, input_string)
124+
125+
if diis_diag_matches:
126+
return float(diis_diag_matches[-1]) # RLast value found for "DIIS/Diag"
127+
128+
return None # No value found
102129

103130
@calcfunction
104131
def check_resize_unit_cell(struct, threshold):

aiida_cp2k/workchains/base.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,19 @@ def overwrite_input_structure(self):
7878
@engine.process_handler(priority=401, exit_codes=[
7979
Cp2kCalculation.exit_codes.ERROR_OUT_OF_WALLTIME,
8080
Cp2kCalculation.exit_codes.ERROR_OUTPUT_INCOMPLETE,
81+
Cp2kCalculation.exit_codes.ERROR_SCF_NOT_CONVERGED,
8182
], enabled=False)
8283
def restart_incomplete_calculation(self, calc):
8384
"""This handler restarts incomplete calculations."""
8485
content_string = calc.outputs.retrieved.base.repository.get_object_content(calc.base.attributes.get('output_filename'))
86+
87+
# Check if time wase exceeded
88+
walltime_exceeded = calc.exit_status == Cp2kCalculation.exit_codes.ERROR_OUT_OF_WALLTIME.status
8589

8690
# CP2K was updating geometry - continue with that.
8791
restart_geometry_transformation = "Max. gradient =" in content_string or "MD| Step number" in content_string
8892
end_inner_scf_loop = "Total energy: " in content_string
93+
8994
# The message is written in the log file when the CP2K input parameter `LOG_PRINT_KEY` is set to True.
9095
if not (restart_geometry_transformation or end_inner_scf_loop or "Writing RESTART" in content_string):
9196
self.report("It seems that the restart of CP2K calculation wouldn't be able to fix the problem as the "
@@ -99,7 +104,12 @@ def restart_incomplete_calculation(self, calc):
99104
params = self.ctx.inputs.parameters
100105

101106
params = utils.add_wfn_restart_section(params, orm.Bool('kpoints' in self.ctx.inputs))
102-
107+
108+
# After version 9.1 Check if calculation aborted due to SCF convergence failure and in case ignore_convergence_failure is set
109+
scf_gradient = utils.get_last_convergence_value(content_string)
110+
scf_restart_thr = 1e-5 # if ABORT for not SCF convergence, but RMS gradient is small, continue
111+
ignore_convergence_failure = "SCF run NOT converged. To continue the calculation regardless" in content_string
112+
is_geo_opt = params.get_dict().get("GLOBAL", {}).get("RUN_TYPE") == "GEO_OPT"
103113
if restart_geometry_transformation:
104114
# Check if we need to fix restart snapshot in REFTRAJ MD
105115
first_snapshot = None
@@ -111,8 +121,14 @@ def restart_incomplete_calculation(self, calc):
111121
pass
112122
params = utils.add_ext_restart_section(params)
113123

124+
if ignore_convergence_failure and scf_gradient and scf_gradient < scf_restart_thr and not walltime_exceeded and is_geo_opt:
125+
# add ignore_convergence_failure to the input parameters
126+
self.report("The SCF was not converged, but the SCF gradient is small and we are doing GEO_OPT. Adding IGNORE_CONVERGENCE_FAILURE.")
127+
params = utils.add_ignore_convergence_failure(params)
128+
114129
self.ctx.inputs.parameters = params # params (new or old ones) that include the necessary restart information.
115130
self.report(
116131
"The CP2K calculation wasn't completed. The restart of the calculation might be able to "
117132
"fix the problem.")
118133
return engine.ProcessHandlerReport(False)
134+
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
###############################################################################
2+
# Copyright (c), The AiiDA-CP2K authors. #
3+
# SPDX-License-Identifier: MIT #
4+
# AiiDA-CP2K is hosted on GitHub at https://github.com/aiidateam/aiida-cp2k #
5+
# For further information on the license, see the LICENSE.txt file. #
6+
###############################################################################
7+
"""An example testing the restart calculation handler for ENERGY run in CP2K."""
8+
9+
import os
10+
import sys
11+
12+
import ase.io
13+
import click
14+
from aiida.common import NotExistent
15+
from aiida.engine import run_get_node
16+
from aiida.orm import Dict, SinglefileData, load_code
17+
from aiida.plugins import DataFactory, WorkflowFactory
18+
19+
Cp2kBaseWorkChain = WorkflowFactory("cp2k.base")
20+
StructureData = DataFactory("core.structure")
21+
22+
23+
def example_base(cp2k_code):
24+
"""Run simple DFT calculation through a workchain."""
25+
26+
thisdir = os.path.dirname(os.path.realpath(__file__))
27+
28+
print("Testing CP2K ENERGY on H2O (DFT) through a workchain...")
29+
30+
# Basis set.
31+
basis_file = SinglefileData(
32+
file=os.path.join(thisdir, "..", "files", "BASIS_MOLOPT")
33+
)
34+
35+
# Pseudopotentials.
36+
pseudo_file = SinglefileData(
37+
file=os.path.join(thisdir, "..", "files", "GTH_POTENTIALS")
38+
)
39+
40+
# Structure.
41+
structure = StructureData(
42+
ase=ase.io.read(os.path.join(thisdir, "..", "files", "h2o.xyz"))
43+
)
44+
45+
# Parameters.
46+
parameters = Dict(
47+
{
48+
"GLOBAL": {
49+
"RUN_TYPE": "GEO_OPT",
50+
"WALLTIME": "00:02:30",
51+
},
52+
"FORCE_EVAL": {
53+
"METHOD": "Quickstep",
54+
"DFT": {
55+
"BASIS_SET_FILE_NAME": "BASIS_MOLOPT",
56+
"POTENTIAL_FILE_NAME": "GTH_POTENTIALS",
57+
"QS": {
58+
"EPS_DEFAULT": 1.0e-16,
59+
"WF_INTERPOLATION": "ps",
60+
"EXTRAPOLATION_ORDER": 3,
61+
},
62+
"MGRID": {
63+
"NGRIDS": 4,
64+
"CUTOFF": 450,
65+
"REL_CUTOFF": 70,
66+
},
67+
"XC": {
68+
"XC_FUNCTIONAL": {
69+
"_": "LDA",
70+
},
71+
},
72+
"POISSON": {
73+
"PERIODIC": "none",
74+
"PSOLVER": "MT",
75+
},
76+
"SCF": {
77+
"MAX_SCF" : 10, # not enough to converge
78+
"EPS_SCF" : "1.e-6",
79+
"PRINT": {"RESTART": {"_": "ON"}}
80+
},
81+
},
82+
"SUBSYS": {
83+
"KIND": [
84+
{
85+
"_": "O",
86+
"BASIS_SET": "DZVP-MOLOPT-SR-GTH",
87+
"POTENTIAL": "GTH-LDA-q6",
88+
},
89+
{
90+
"_": "H",
91+
"BASIS_SET": "DZVP-MOLOPT-SR-GTH",
92+
"POTENTIAL": "GTH-LDA-q1",
93+
},
94+
],
95+
},
96+
},
97+
}
98+
)
99+
100+
# Construct process builder.
101+
builder = Cp2kBaseWorkChain.get_builder()
102+
103+
# Switch on resubmit_unconverged_geometry disabled by default.
104+
builder.handler_overrides = Dict(
105+
{"restart_incomplete_calculation": {"enabled": True}}
106+
)
107+
108+
# Input structure.
109+
builder.cp2k.structure = structure
110+
builder.cp2k.parameters = parameters
111+
builder.cp2k.code = cp2k_code
112+
builder.cp2k.file = {
113+
"basis": basis_file,
114+
"pseudo": pseudo_file,
115+
}
116+
builder.cp2k.metadata.options = {
117+
"resources": {
118+
"num_machines": 1,
119+
"num_mpiprocs_per_machine": 1,
120+
},
121+
"max_wallclock_seconds": 1 * 1 * 60,
122+
}
123+
124+
print("Submitted calculation...")
125+
_, process_node = run_get_node(builder)
126+
127+
if process_node.exit_status == 0:
128+
print("Work chain is finished correctly.")
129+
else:
130+
print("ERROR! Work chain failed.")
131+
sys.exit(3)
132+
133+
134+
@click.command("cli")
135+
@click.argument("codelabel")
136+
def cli(codelabel):
137+
"""Click interface."""
138+
try:
139+
code = load_code(codelabel)
140+
except NotExistent:
141+
print(f"The code '{codelabel}' does not exist")
142+
sys.exit(1)
143+
example_base(code)
144+
145+
146+
if __name__ == "__main__":
147+
cli()

0 commit comments

Comments
 (0)