• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

bjmorgan / vasppy / 18817921901

26 Oct 2025 12:28PM UTC coverage: 0.0% (-35.8%) from 35.753%
18817921901

push

github

bjmorgan
Ruff fix

0 of 1 new or added line in 1 file covered. (0.0%)

823 existing lines in 21 files now uncovered.

0 of 2176 relevant lines covered (0.0%)

0.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/vasppy/scripts/checkforce.py
1
#! /usr/bin/env python3
2

3
import argparse
×
4
import re
×
5
import numpy as np
×
6
from dataclasses import dataclass, field
×
7

UNCOV
8
from vasppy.outcar import forces_from_outcar
×
9

10

UNCOV
11
@dataclass
×
UNCOV
12
class ForcesData:
×
UNCOV
13
    forces: np.ndarray = field(repr=False)
×
14
    convergence: float
×
15
    number_of_ions: int = field(init=False)
×
16
    mean_excess_force: float = field(init=False)
×
17
    max_force: float = field(init=False)
×
18
    n_not_converged: int = field(init=False)
×
19

20
    def __post_init__(self):
×
21
        if isinstance(self.forces, list):
×
UNCOV
22
            self.forces = np.array(self.forces)
×
23
        self.number_of_ions = self.forces.shape[0]
×
24
        force_magnitude = np.sqrt(np.sum(self.forces**2, axis=1))
×
25
        self.mean_excess_force = (
×
26
            np.where(
27
                force_magnitude > self.convergence,
28
                force_magnitude - self.convergence,
29
                0,
30
            ).sum()
31
            / self.number_of_ions
32
        )
UNCOV
33
        self.n_not_converged = np.sum(force_magnitude > self.convergence)
×
UNCOV
34
        self.max_force = force_magnitude.max()
×
35

36
    def print_forces(self) -> None:
×
37
        for theseforces in self.forces:
×
UNCOV
38
            output_str = [f"  {force: .6f}" for force in theseforces]
×
39
            for force in theseforces:
×
40
                if abs(force) > self.convergence:
×
41
                    output_str.append("   ****")
×
42
                else:
43
                    output_str.append("   conv")
×
44
            force_norm = np.linalg.norm(theseforces)
×
UNCOV
45
            output_str.append(f"   {force_norm:.6f}")
×
46
            if np.linalg.norm(force_norm) > self.convergence:
×
47
                output_str.append("   ****")
×
48
            else:
49
                output_str.append("   conv")
×
50
            print("".join(output_str))
×
51

52

53
def parse_args() -> argparse.Namespace:
×
UNCOV
54
    parser = argparse.ArgumentParser(
×
55
        description="Checks for convergence of geometry optimisations in VASP"
56
    )
57
    parser.add_argument(
×
58
        "-o",
59
        "--outcar",
60
        type=str,
61
        default="OUTCAR",
62
        help='The filepath of the OUTCAR file to be parsed. Default is "OUTCAR")',
63
    )
UNCOV
64
    parser.add_argument(
×
65
        "-c",
66
        "--convergence",
67
        type=float,
68
        help="Set force convergence. Default is to read the convergence from the OUTCAR file.",
69
    )
70
    # Create mutually exclusive group for --verbose and --all
UNCOV
71
    output_group = parser.add_mutually_exclusive_group()
×
UNCOV
72
    output_group.add_argument(
×
73
        "-v",
74
        "--verbose",
75
        action="store_true",
76
        help="Verbose output. Show convergence status for all atoms.",
77
    )
UNCOV
78
    output_group.add_argument(
×
79
        "-a",
80
        "--all",
81
        action="store_true",
82
        help="Print summary data for every ionic step.",
83
    )
84
    args = parser.parse_args()
×
UNCOV
85
    return args
×
86

87

UNCOV
88
def get_forces_data(
×
89
    outcar_filename: str = "OUTCAR",
90
    convergence: float | None = None,
91
) -> ForcesData:
92
    """Parse an OUTCAR file and return forces data, including various summary statistics.
93

94
    Args:
95
        outcar_filename: OUTCAR filename. Default is "OUTCAR".
96
        convergence: Optionally set the forces convergence threshold.
97
            Default is to read from the OUTCAR file.
98

99
    Returns:
100
        ForcesData object with forces and summary statistics.
101

102
    """
103
    if not convergence:
×
104
        convergence = read_ediffg_from_outcar(outcar_filename)
×
105
    forces = forces_from_outcar(outcar_filename, last_one_only=True)
×
106
    forces_data = ForcesData(forces=forces, convergence=convergence)
×
107
    return forces_data
×
108

109
def get_all_forces_data(
×
110
    outcar_filename: str = "OUTCAR",
111
    convergence: float | None = None,
112
):
113
    """Parse an OUTCAR file and yield forces data for all ionic steps.
114

115
    Args:
116
        outcar_filename: OUTCAR filename. Default is "OUTCAR".
117
        convergence: Optionally set the forces convergence threshold.
118
            Default is to read from the OUTCAR file.
119

120
    Yields:
121
        ForcesData object for each ionic step.
122

123
    """
UNCOV
124
    if not convergence:
×
125
        convergence = read_ediffg_from_outcar(outcar_filename)
×
126
    all_forces = forces_from_outcar(outcar_filename, last_one_only=False)
×
UNCOV
127
    for step_forces in all_forces:
×
UNCOV
128
        yield ForcesData(forces=step_forces, convergence=convergence)
×
129

UNCOV
130
def read_ediffg_from_outcar(outcar_filename: str) -> float:
×
131
    """Read EDIFFG convergence criterion from OUTCAR file.
132

133
    Args:
134
        outcar_filename: OUTCAR filename.
135

136
    Returns:
137
        Convergence threshold (absolute value of EDIFFG).
138

139
    Raises:
140
        ValueError: If EDIFFG cannot be found in the file.
141

142
    """
UNCOV
143
    with open(outcar_filename, "r") as f:
×
144
        outcar = f.read()
×
145
    convergence_re = re.compile(r"EDIFFG = -([\d\.E-]+)")
×
146
    try:
×
147
        convergence = float(convergence_re.findall(outcar)[0])
×
148
    except IndexError as exc:
×
UNCOV
149
        raise ValueError("Unable to read EDIFFG.") from exc
×
UNCOV
150
    return convergence
×
151

152
def main() -> None:
×
UNCOV
153
    args = parse_args()
×
154

155
    if args.all:
×
156
        # Print summary for all ionic steps
157
        print(f"{'Max Force':<12}{'Non-opt':<10}{'Mean Excess':<10}")
×
NEW
158
        for forces_data in get_all_forces_data(
×
159
                outcar_filename=args.outcar, convergence=args.convergence
160
            ):
161
            print(
×
162
                f"{forces_data.max_force:<12.6f}"
163
                f"{forces_data.n_not_converged:<10}"
164
                f"{forces_data.mean_excess_force:<10.6f}"
165
            )
166
    else:
167
        # Original behavior: print only last step
UNCOV
168
        forces_data = get_forces_data(
×
169
            outcar_filename=args.outcar, convergence=args.convergence
170
        )
UNCOV
171
        if args.verbose:
×
UNCOV
172
            forces_data.print_forces()
×
UNCOV
173
            print()
×
UNCOV
174
        print(f"remainder:  {forces_data.mean_excess_force:.6f}")
×
UNCOV
175
        print(f"maximum:    {forces_data.max_force:.6f}")
×
UNCOV
176
        print(f"non-opt:    {forces_data.n_not_converged} / {forces_data.number_of_ions}")
×
177

UNCOV
178
if __name__ == "__main__":
×
UNCOV
179
    main()
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc