Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
- Updated deprecated `typing` types to be compliant with Python >=3.11 guidelines (#216)
- Fixed a typo in the badge for the OPI paper (#222).
- Fixed `Structure.nelectrons` for structures containing ghost atoms (#268).
- Fixed external xTB SCF convergence check in `scf_converged()` (#272).

## [2.0.0] - 2026-02-10

Expand Down
5 changes: 5 additions & 0 deletions examples/exmp057_external_xtb_scf/inp.xyz
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
3

O -3.56626 1.77639 0.00000
H -2.59626 1.77639 0.00000
H -3.88959 1.36040 -0.81444
58 changes: 58 additions & 0 deletions examples/exmp057_external_xtb_scf/job.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env python3
"""
Example: External xTB calculation with SCF convergence check.

Runs GFN2-xTB using the external xTB calculation method and checks that
`output.scf_converged()` properly detects convergence in the .out file.
"""

import shutil
import sys
from pathlib import Path

from opi.core import Calculator
from opi.input.simple_keywords import Scf, Sqm, Task
from opi.input.structures import Structure
from opi.output.core import Output


def run_exmp057(
structure: Structure | None = None, working_dir: Path | None = Path("RUN")
) -> Output:
# > recreate the working dir
shutil.rmtree(working_dir, ignore_errors=True)
working_dir.mkdir()

# > if no structure is given read structure from inp.xyz
if structure is None:
structure = Structure.from_xyz("inp.xyz")

calc = Calculator(basename="job", working_dir=working_dir)
calc.structure = structure
calc.input.add_simple_keywords(Scf.NOAUTOSTART, Sqm.GFN2_XTB, Task.SP)

calc.write_input()
calc.run()

# > Get output without version check since it is an external method
output = calc.get_output(version_check=False)
if not output.terminated_normally():
print(f"ORCA calculation failed, see output file: {output.get_outfile()}")
print(output.error_message())
sys.exit(1)

# > parse output properties
output.parse()

# > check for convergence of the external xTB SCF
if output.scf_converged():
print("EXTERNAL XTB SCF CONVERGED")
else:
print("EXTERNAL XTB SCF DID NOT CONVERGE")
sys.exit(1)

return output


if __name__ == "__main__":
run_exmp057()
1 change: 1 addition & 0 deletions src/opi/output/grepper/patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# > Success strings - String that indicate something finished with success
TERMINATED_NORMALLY = "****ORCA TERMINATED NORMALLY****"
SCF_CONVERGED = "SUCCESS"
EXTERNAL_XTB_SCF_CONVERGED = "convergence criteria satisfied after"
GEOMETRY_CONVERGED = "HURRAY"
CC_CONVERGED = "The Coupled-Cluster iterations have converged"
CASSCF_CONVERGED = "---- THE CAS-SCF GRADIENT HAS CONVERGED ----"
Expand Down
7 changes: 5 additions & 2 deletions src/opi/output/grepper/recipes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
CASSCF_CONVERGED,
CC_CONVERGED,
ERROR_PATTERNS,
EXTERNAL_XTB_SCF_CONVERGED,
GEOMETRY_CONVERGED,
HAS_ABORTING,
HAS_FREQ,
Expand Down Expand Up @@ -280,7 +281,7 @@ def has_scf(file_name: Path, /) -> bool:

def has_scf_converged(file_name: Path, /) -> bool:
"""
Searches for the message 'SUCCESS' as indicator that the SCF converged.
Searches for the message 'SUCCESS' or 'convergence criteria satisfied after' as indicator that the SCF converged.

Parameter
---------
Expand All @@ -292,7 +293,9 @@ def has_scf_converged(file_name: Path, /) -> bool:
bool
True if expression is found in file else False
"""
return has_string_in_file(file_name, SCF_CONVERGED)
return has_string_in_file(file_name, SCF_CONVERGED) or has_string_in_file(
file_name, EXTERNAL_XTB_SCF_CONVERGED
)


def has_casscf_converged(file_name: Path, /) -> bool:
Expand Down
15 changes: 15 additions & 0 deletions tests/examples/test_exmp057_external_xtb_scf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import pytest

from examples.exmp057_external_xtb_scf.job import run_exmp057
from opi.input.structures import Structure


@pytest.mark.examples
@pytest.mark.orca
def test_exmp057_external_xtb_scf(example_input_file, tmp_path) -> None:
"""Ensure external xTB calculation example runs and scf_converged detects convergence."""
input_file = example_input_file(run_exmp057)
structure = Structure.from_xyz(input_file)

output = run_exmp057(structure=structure, working_dir=tmp_path)
assert output.scf_converged() is True
1 change: 1 addition & 0 deletions tests/unit/test_output_get_values.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,4 @@ def test_get_nelectrons_nonexistent(empty_output_object: Output):
def test_get_nbf_nonexistent(empty_output_object: Output):
"""Test to check if `Output.get_nbf()` returns None when expected."""
assert not empty_output_object.get_nbf()

Loading