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

stfc / janus-core / 8325072180

18 Mar 2024 10:32AM UTC coverage: 91.525% (-0.04%) from 91.566%
8325072180

Pull #70

github

web-flow
Merge 8d74312d2 into 7d9ea5872
Pull Request #70: Add geomopt to CLI

51 of 58 new or added lines in 4 files covered. (87.93%)

2 existing lines in 1 file now uncovered.

270 of 295 relevant lines covered (91.53%)

3.66 hits per line

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

86.79
/janus_core/cli.py
1
"""Set up commandline interface."""
2

3
import ast
4✔
4
from pathlib import Path
4✔
5
from typing import Annotated
4✔
6

7
import typer
4✔
8

9
from janus_core.geom_opt import optimize
4✔
10
from janus_core.single_point import SinglePoint
4✔
11

12
app = typer.Typer()
4✔
13

14

15
class TyperDict:  #  pylint: disable=too-few-public-methods
4✔
16
    """
17
    Custom dictionary for typer.
18

19
    Parameters
20
    ----------
21
    value : str
22
        Value of string representing a dictionary.
23
    """
24

25
    def __init__(self, value: str):
4✔
26
        """
27
        Initialise class.
28

29
        Parameters
30
        ----------
31
        value : str
32
            Value of string representing a dictionary.
33
        """
34
        self.value = value
4✔
35

36
    def __str__(self):
4✔
37
        """
38
        String representation of class.
39

40
        Returns
41
        -------
42
        str
43
            Class name and value of string representing a dictionary.
44
        """
45
        return f"<TyperDict: value={self.value}>"
×
46

47

48
def parse_dict_class(value: str):
4✔
49
    """
50
    Convert string input into a dictionary.
51

52
    Parameters
53
    ----------
54
    value : str
55
        String representing dictionary to be parsed.
56

57
    Returns
58
    -------
59
    TyperDict
60
        Parsed string as a dictionary.
61
    """
62
    return TyperDict(ast.literal_eval(value))
4✔
63

64

65
# Shared type aliases
66
StructPath = Annotated[
4✔
67
    Path, typer.Option("--struct", help="Path of structure to simulate")
68
]
69
Architecture = Annotated[
4✔
70
    str, typer.Option("--arch", help="MLIP architecture to use for calculations")
71
]
72
Device = Annotated[str, typer.Option(help="Device to run calculations on")]
4✔
73
ReadKwargs = Annotated[
4✔
74
    TyperDict,
75
    typer.Option(
76
        parser=parse_dict_class,
77
        help="Keyword arguments to pass to ase.io.read  [default: {}]",
78
        metavar="DICT",
79
    ),
80
]
81
CalcKwargs = Annotated[
4✔
82
    TyperDict,
83
    typer.Option(
84
        parser=parse_dict_class,
85
        help="Keyword arguments to pass to selected calculator  [default: {}]",
86
        metavar="DICT",
87
    ),
88
]
89
WriteKwargs = Annotated[
4✔
90
    TyperDict,
91
    typer.Option(
92
        parser=parse_dict_class,
93
        help=(
94
            "Keyword arguments to pass to ase.io.write when saving "
95
            "results [default: {}]"
96
        ),
97
        metavar="DICT",
98
    ),
99
]
100
LogFile = Annotated[Path, typer.Option("--log", help="Path to save logs to")]
4✔
101

102

103
@app.command()
4✔
104
def singlepoint(
4✔
105
    struct_path: StructPath,
106
    architecture: Architecture = "mace_mp",
107
    device: Device = "cpu",
108
    properties: Annotated[
109
        list[str],
110
        typer.Option(
111
            "--property",
112
            help=(
113
                "Properties to calculate. If not specified, 'energy', 'forces' "
114
                "and 'stress' will be returned."
115
            ),
116
        ),
117
    ] = None,
118
    read_kwargs: ReadKwargs = None,
119
    calc_kwargs: CalcKwargs = None,
120
    write_kwargs: WriteKwargs = None,
121
    log_file: LogFile = "singlepoint.log",
122
):
123
    """
124
    Perform single point calculations and save to file.
125

126
    Parameters
127
    ----------
128
    struct_path : Path
129
        Path of structure to simulate.
130
    architecture : Optional[str]
131
        MLIP architecture to use for single point calculations.
132
        Default is "mace_mp".
133
    device : Optional[str]
134
        Device to run model on. Default is "cpu".
135
    properties : Optional[str]
136
        Physical properties to calculate. Default is "energy".
137
    read_kwargs : Optional[dict[str, Any]]
138
        Keyword arguments to pass to ase.io.read. Default is {}.
139
    calc_kwargs : Optional[dict[str, Any]]
140
        Keyword arguments to pass to the selected calculator. Default is {}.
141
    write_kwargs : Optional[dict[str, Any]]
142
        Keyword arguments to pass to ase.io.write when saving results. Default is {}.
143
    log_file : Optional[Path]
144
        Path to write logs to. Default is "singlepoint.log".
145
    """
146
    read_kwargs = read_kwargs.value if read_kwargs else {}
4✔
147
    calc_kwargs = calc_kwargs.value if calc_kwargs else {}
4✔
148
    write_kwargs = write_kwargs.value if write_kwargs else {}
4✔
149

150
    if not isinstance(read_kwargs, dict):
4✔
151
        raise ValueError("read_kwargs must be a dictionary")
×
152
    if not isinstance(calc_kwargs, dict):
4✔
153
        raise ValueError("calc_kwargs must be a dictionary")
×
154
    if not isinstance(write_kwargs, dict):
4✔
155
        raise ValueError("write_kwargs must be a dictionary")
×
156

157
    s_point = SinglePoint(
4✔
158
        struct_path=struct_path,
159
        architecture=architecture,
160
        device=device,
161
        read_kwargs=read_kwargs,
162
        calc_kwargs=calc_kwargs,
163
        log_kwargs={"filename": log_file, "filemode": "w"},
164
    )
165
    s_point.run_single_point(
4✔
166
        properties=properties, write_results=True, write_kwargs=write_kwargs
167
    )
168

169

170
@app.command()
4✔
171
def geomopt(  # pylint: disable=too-many-arguments,too-many-locals
4✔
172
    struct_path: StructPath,
173
    fmax: Annotated[
174
        float, typer.Option("--max-force", help="Maximum force for convergence")
175
    ] = 0.1,
176
    architecture: Architecture = "mace_mp",
177
    device: Device = "cpu",
178
    fully_opt: Annotated[
179
        bool,
180
        typer.Option(
181
            "--fully-opt",
182
            help="Fully optimize the cell vectors, angles, and atomic positions",
183
        ),
184
    ] = False,
185
    vectors_only: Annotated[
186
        bool,
187
        typer.Option("--vectors-only", help="Allow only cell vectors to change"),
188
    ] = False,
189
    read_kwargs: ReadKwargs = None,
190
    calc_kwargs: CalcKwargs = None,
191
    write_kwargs: WriteKwargs = None,
192
    traj_file: Annotated[
193
        str, typer.Option("--traj", help="Path to save optimization frames to")
194
    ] = None,
195
    log_file: LogFile = "geomopt.log",
196
):
197
    """
198
    Perform geometry optimization and save optimized structure to file.
199

200
    Parameters
201
    ----------
202
    struct_path : Path
203
        Path of structure to simulate.
204
    fmax : float
205
        Set force convergence criteria for optimizer in units eV/Å.
206
        Default is 0.1.
207
    architecture : Optional[str]
208
        MLIP architecture to use for geometry optimization.
209
        Default is "mace_mp".
210
    device : Optional[str]
211
        Device to run model on. Default is "cpu".
212
    fully_opt : bool
213
        Whether to optimize the cell as well as atomic positions. Default is False.
214
    vectors_only : bool
215
        Whether to allow only hydrostatic deformations. Default is False.
216
    read_kwargs : Optional[dict[str, Any]]
217
        Keyword arguments to pass to ase.io.read. Default is {}.
218
    calc_kwargs : Optional[dict[str, Any]]
219
        Keyword arguments to pass to the selected calculator. Default is {}.
220
    write_kwargs : Optional[dict[str, Any]]
221
        Keyword arguments to pass to ase.io.write when saving results. Default is {}.
222
    traj_file : Optional[str]
223
        Path to save optimization trajectory to. Default is None.
224
    log_file : Optional[Path]
225
        Path to write logs to. Default is "geomopt.log".
226
    """
227
    read_kwargs = read_kwargs.value if read_kwargs else {}
4✔
228
    calc_kwargs = calc_kwargs.value if calc_kwargs else {}
4✔
229
    write_kwargs = write_kwargs.value if write_kwargs else {}
4✔
230

231
    if not isinstance(read_kwargs, dict):
4✔
NEW
232
        raise ValueError("read_kwargs must be a dictionary")
×
233
    if not isinstance(calc_kwargs, dict):
4✔
NEW
234
        raise ValueError("calc_kwargs must be a dictionary")
×
235
    if not isinstance(write_kwargs, dict):
4✔
NEW
236
        raise ValueError("write_kwargs must be a dictionary")
×
237

238
    if not fully_opt and vectors_only:
4✔
239
        raise ValueError("--vectors-only requires --fully-opt to be set")
4✔
240

241
    # Set up single point calculator
242
    s_point = SinglePoint(
4✔
243
        struct_path=struct_path,
244
        architecture=architecture,
245
        device=device,
246
        read_kwargs=read_kwargs,
247
        calc_kwargs=calc_kwargs,
248
        log_kwargs={"filename": log_file, "filemode": "w"},
249
    )
250

251
    opt_kwargs = {"trajectory": traj_file} if traj_file else None
4✔
252
    traj_kwargs = {"filename": traj_file} if traj_file else None
4✔
253
    filter_kwargs = {"hydrostatic_strain": vectors_only} if fully_opt else None
4✔
254

255
    # Use default filter if passed --fully-opt, otherwise override with None
256
    fully_opt_dict = {} if fully_opt else {"filter_func": None}
4✔
257

258
    # Run geometry optimization and save output structure
259
    optimize(
4✔
260
        s_point.struct,
261
        fmax=fmax,
262
        filter_kwargs=filter_kwargs,
263
        **fully_opt_dict,
264
        opt_kwargs=opt_kwargs,
265
        write_results=True,
266
        write_kwargs=write_kwargs,
267
        traj_kwargs=traj_kwargs,
268
        log_kwargs={"filename": log_file, "filemode": "a"},
269
    )
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

© 2025 Coveralls, Inc