-
Notifications
You must be signed in to change notification settings - Fork 27
Minimal implementation of error_message #222
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
b4665b8
002c153
1eaf2ea
d7e0dba
5d69098
cac8c6e
82e7a9f
d047415
04a65e2
f2d435e
aba93cc
24c8a3e
685e317
d1c9b7a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -127,6 +127,9 @@ class BlockMethod(Block): | |
| d3s8: float | None = None | ||
| d3a2: float | None = None | ||
|
|
||
| # > Number of CPSCF iterations | ||
| z_maxiter: int | None = None | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see this change listed in the PR description. Please cross-check. |
||
|
|
||
| # > Options for Extopt | ||
| ProgExt: InputFilePath | None = None # Path to wrapper script | ||
| Ext_Params: str | None = None # Arbitrary optional command line arguments | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,8 +18,11 @@ | |
| from opi.output.cube import CubeOutput | ||
| from opi.output.gbw_suffix import GbwSuffix | ||
| from opi.output.grepper.recipes import ( | ||
| get_error_message, | ||
| get_float_from_line, | ||
| get_lines_from_block, | ||
| has_casscf_converged, | ||
| has_cc_converged, | ||
| has_geometry_optimization_converged, | ||
| has_scf_converged, | ||
| has_terminated_normally, | ||
|
|
@@ -672,6 +675,13 @@ def terminated_normally(self) -> bool: | |
| except FileNotFoundError: | ||
| return False | ||
|
|
||
| def error_message(self) -> str | None: | ||
| outfile = self.get_outfile() | ||
| try: | ||
| return get_error_message(outfile) | ||
| except FileNotFoundError: | ||
| return "Output File Not Found" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would add the expected path ;) |
||
|
|
||
| def scf_converged(self) -> bool: | ||
| """ | ||
| Determine if ORCA SCF converged, by looking for "SUCCESS" in the ".out" file. | ||
|
|
@@ -689,6 +699,40 @@ def scf_converged(self) -> bool: | |
| except FileNotFoundError: | ||
| return False | ||
|
|
||
| def casscf_converged(self) -> bool: | ||
| """ | ||
| Determine if ORCA CAS-SCF converged, by looking for "THE CAS-SCF GRADIENT HAS CONVERGED" in the ".out" file. | ||
| Check only if ORCA CAS-SCF was actually requested. | ||
| If the ".out" file does not exist, also return False. | ||
|
|
||
| Returns | ||
| ------- | ||
| bool | ||
| True if string is found in ".out" file else False | ||
| """ | ||
| outfile = self.get_outfile() | ||
| try: | ||
| return has_casscf_converged(outfile) | ||
| except FileNotFoundError: | ||
| return False | ||
|
Comment on lines
+713
to
+717
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These lines seem to develop into a repeating pattern. We should write a driver for has_xxx-function. Something like:
|
||
|
|
||
| def cc_converged(self) -> bool: | ||
| """ | ||
| Determine if ORCA coupled-cluster iterations converged, by looking for "The Coupled-Cluster iterations have converged" in the ".out" file. | ||
| Check only if CC was actually requested. | ||
| If the ".out" file does not exist, also return False. | ||
|
|
||
| Returns | ||
| ------- | ||
| bool | ||
| True if string is found in ".out" file else False | ||
| """ | ||
| outfile = self.get_outfile() | ||
| try: | ||
| return has_cc_converged(outfile) | ||
| except FileNotFoundError: | ||
| return False | ||
|
|
||
| def geometry_optimization_converged(self) -> bool: | ||
| """ | ||
| Determine if ORCA geometry optimization converged, by looking for "HURRAY" in the ".out" file. | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,152 @@ | ||||||
| # patterns.py | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not done consistently adding the filename in the first line as comment. |
||||||
| import re | ||||||
| from typing import Callable | ||||||
|
|
||||||
| from opi.output.grepper.core import Grepper | ||||||
|
|
||||||
|
|
||||||
| class ErrorPattern: | ||||||
| """ | ||||||
| Represents an error pattern in the ORCA output file. | ||||||
| More complex error patterns derive from this class and override the extractor | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| Attributes | ||||||
| ---------- | ||||||
| grep_string: str | ||||||
| The string that is searched in the output file. | ||||||
| message: str | ||||||
| A human-readable error message of the given error pattern. | ||||||
| critical: bool, default = False | ||||||
| When the error is critical we will stop searching for further errors after finding it. | ||||||
| extractor: Callable[[Grepper], str] | None, default = None | ||||||
| Optional function for extracting more details from the matched line. | ||||||
|
|
||||||
| """ | ||||||
|
|
||||||
| def __init__( | ||||||
| self, | ||||||
| grep_string: str | None = None, | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not just go with empty strings as default? I don't see the value of using |
||||||
| message: str | None = None, | ||||||
| critical: bool = False, | ||||||
| extractor: Callable[[Grepper], str] | None = None, | ||||||
| ) -> None: | ||||||
| if grep_string is not None: | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would check as follows: |
||||||
| self.grep_string = grep_string | ||||||
| if message is not None: | ||||||
| self.message = message | ||||||
| if critical is not None: | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Above it says |
||||||
| self.critical = critical | ||||||
| self.extractor = extractor | ||||||
|
|
||||||
| def match(self, grepper: Grepper) -> str | None: | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||
| hit = grepper.search(self.grep_string, case_sensitive=True) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I actually don't see the point why we would first search for |
||||||
| if not hit: | ||||||
| return None | ||||||
| if self.extractor: | ||||||
| return self.extractor(grepper) | ||||||
| return self.extract(grepper) or self.message | ||||||
|
|
||||||
| def extract(self, grepper: Grepper) -> str | None: | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method should have a docstring. Especially in the base class. |
||||||
| return None | ||||||
|
|
||||||
|
|
||||||
| class InvalidLineError(ErrorPattern): | ||||||
| """ | ||||||
| Triggered when ORCA encounters an invalid line in the input file. | ||||||
| This typically means a line does not start with a valid ORCA input | ||||||
| character such as '$', '!', '%', '*' or '['. | ||||||
| """ | ||||||
|
|
||||||
| grep_string = "ERROR: expect a '$', '!', '%', '*' or '[' in the input" | ||||||
| message = "Invalid input line in ORCA input" | ||||||
| critical = True | ||||||
|
|
||||||
| def extract(self, grepper: Grepper) -> str | None: | ||||||
| match = grepper.search(self.grep_string, case_sensitive=True, skip_lines=1) | ||||||
| if match: | ||||||
| m = re.search(r"\((.+?)\)", match[0]) | ||||||
| result = m.group(1) if m else None | ||||||
| return f"Invalid line starting with: {result}" if result else None | ||||||
| return None | ||||||
|
|
||||||
|
|
||||||
| class SimpleKeywordsError(ErrorPattern): | ||||||
| """ | ||||||
| Triggered when ORCA encounters an unrecognized or duplicated keyword | ||||||
| in the simple input line (the '!' line). | ||||||
| """ | ||||||
|
|
||||||
| grep_string = "UNRECOGNIZED OR DUPLICATED KEYWORD(S) IN SIMPLE INPUT LINE" | ||||||
| message = "An unrecognized or duplicated simple keyword was requested" | ||||||
| critical = True | ||||||
|
|
||||||
| def extract(self, grepper: Grepper) -> str | None: | ||||||
| match = grepper.search(self.grep_string, case_sensitive=True, skip_lines=1) | ||||||
| return f"Unknown/duplicate simple keyword(s): {match[0]}" if match else None | ||||||
|
|
||||||
|
|
||||||
| class UnknownBlockError(ErrorPattern): | ||||||
| """ | ||||||
| Triggered when ORCA encounters an unknown block name in the input file, | ||||||
| i.e. a '%blockname' that ORCA does not recognize. | ||||||
| """ | ||||||
|
|
||||||
| grep_string = "Unknown identifier" | ||||||
| message = "An unknown block was requested" | ||||||
| critical = True | ||||||
|
|
||||||
| def extract(self, grepper: Grepper) -> str | None: | ||||||
| match = grepper.search(self.grep_string, case_sensitive=True, skip_lines=0) | ||||||
| return f"Unknown block: {match[0].split()[-1]}" if match else None | ||||||
|
|
||||||
|
|
||||||
| class UnknownBlockKeyError(ErrorPattern): | ||||||
| """ | ||||||
| Triggered when ORCA encounters an unknown key inside a block, | ||||||
| i.e. a valid block name but an unrecognized option within it. | ||||||
| """ | ||||||
|
|
||||||
| grep_string = "Unknown identifier in" | ||||||
| message = "An unknown block option was requested" | ||||||
| critical = True | ||||||
|
|
||||||
| def extract(self, grepper: Grepper) -> str | None: | ||||||
| match = grepper.search(self.grep_string, case_sensitive=True, skip_lines=1) | ||||||
| return f"Unknown block key: {match[0].split(':')[-1]}" if match else None | ||||||
|
|
||||||
|
|
||||||
| class UnknownBlockValueError(ErrorPattern): | ||||||
| """ | ||||||
| Triggered when ORCA encounters an invalid value for a block option, | ||||||
| i.e. the key is recognized but the assigned value is not valid. | ||||||
| """ | ||||||
|
|
||||||
| grep_string = "Invalid assignment" | ||||||
| message = "An invalid value was requested in a block" | ||||||
| critical = True | ||||||
|
|
||||||
| def extract(self, grepper: Grepper) -> str | None: | ||||||
| match = grepper.search(self.grep_string, case_sensitive=True, skip_lines=1) | ||||||
| return f"Unknown block value: {match[0].split(':')[-1]}" if match else None | ||||||
|
|
||||||
|
|
||||||
| class NotEnoughMemoryScfError(ErrorPattern): | ||||||
| """ | ||||||
| Triggered when there is not enough memory available for the SCF | ||||||
| """ | ||||||
|
|
||||||
| grep_string = "Error (ORCA_SCF): Not enough memory available!" | ||||||
| message = "Not enough memory for SCF available" | ||||||
| critical = True | ||||||
|
|
||||||
| def extract(self, grepper: Grepper) -> str | None: | ||||||
| mem_avail = grepper.search(self.grep_string, case_sensitive=True, skip_lines=1)[-1].split( | ||||||
| ":" | ||||||
| )[-1] | ||||||
| mem_estimated = grepper.search(self.grep_string, case_sensitive=True, skip_lines=2)[ | ||||||
| -1 | ||||||
| ].split(":")[-1] | ||||||
| if mem_estimated and mem_avail: | ||||||
| return f"Not enough memory available for SCF. Available: {mem_avail}, Required: {mem_estimated}" | ||||||
| else: | ||||||
| return None | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| from opi.output.grepper.error_pattern import ( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add module level docstring, describing what this file holds. |
||
| ErrorPattern, | ||
| InvalidLineError, | ||
| NotEnoughMemoryScfError, | ||
| SimpleKeywordsError, | ||
| UnknownBlockError, | ||
| UnknownBlockKeyError, | ||
| UnknownBlockValueError, | ||
| ) | ||
|
|
||
| # > Success strings | ||
| TERMINATED_NORMALLY = "****ORCA TERMINATED NORMALLY****" | ||
| SCF_CONVERGED = "SUCCESS" | ||
| GEOMETRY_CONVERGED = "HURRAY" | ||
| CC_CONVERGED = "The Coupled-Cluster iterations have converged" | ||
| CASSCF_CONVERGED = "---- THE CAS-SCF GRADIENT HAS CONVERGED ----" | ||
|
|
||
| # > Has strings | ||
| HAS_GEOMETRY_OPT = "Geometry Optimization Run" | ||
| HAS_SCF = "SCF SETTINGS" | ||
| HAS_ABORTING = "aborting" | ||
|
|
||
| # > Error patterns in order of priority. | ||
| # > Critical errors will stop scanning when matched. | ||
| # > Non-critical errors will just be added and reported. | ||
| ERROR_PATTERNS: list[ErrorPattern] = [ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add kind of line in the form of a comment, at which point in the list we go from critical to non-critical. |
||
| # > Critical input errors - stop scanning on first match | ||
| InvalidLineError(), | ||
| SimpleKeywordsError(), | ||
| UnknownBlockValueError(), | ||
| UnknownBlockKeyError(), | ||
| UnknownBlockError(), | ||
| ErrorPattern( | ||
| "You must have a [COORDS] ... [END] block in your input", | ||
| "No coordinates in the ORCA input.", | ||
| critical=True, | ||
| ), | ||
| # > Convergence errors | ||
| ErrorPattern( | ||
| "Error (SHARK/CP-SCF Solver): Unfortunately, the calculation did not converge.", | ||
| "CP-SCF did not converge", | ||
| critical=True, | ||
| ), | ||
| ErrorPattern( | ||
| "The Coupled-Cluster iterations have NOT converged", | ||
| "Coupled-Cluster did not converge", | ||
| critical=True, | ||
| ), | ||
| ErrorPattern("CIS/TDA-DFT did not converge", "CIS/TDA-DFT did not converge"), | ||
| ErrorPattern("SCF NOT CONVERGED", "SCF did not converge", critical=True), | ||
| ErrorPattern( | ||
| "The optimization did not converge", | ||
| "Geometry optimization did not converge", | ||
| critical=False, | ||
| ), | ||
| # > Memory Errors | ||
| NotEnoughMemoryScfError(), | ||
| ErrorPattern( | ||
| "Error (ORCA_MDCI): not enough memory for computing triples", | ||
| "Not enough memory for triples calculation", | ||
| critical=True, | ||
| ), | ||
| ErrorPattern("ERROR - OUT OF MEMORY !!!", "Calculation ran out of memory", critical=False), | ||
| # > Module terminates not normally | ||
| ErrorPattern( | ||
| "ORCA finished by error termination in MDCI", | ||
| "Error in MDCI part of the calculation", | ||
| critical=True, | ||
| ), | ||
| ErrorPattern( | ||
| "ORCA finished by error termination in MP2", | ||
| "Error in MP2 part of the calculation", | ||
| critical=True, | ||
| ), | ||
| # > Potentially MPI related error | ||
| ErrorPattern("-" * 74, "Potentially an Open MPI related error occurred.", critical=False), | ||
| # > Unspecific errors | ||
| ErrorPattern("ABORTING THE RUN", "ORCA aborted the run"), | ||
| ErrorPattern("ERROR", "ORCA encountered an error"), | ||
| ] | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this change listed in the PR description?