3
3
from __future__ import annotations
4
4
5
5
import itertools
6
+ import warnings
6
7
import xml .etree .ElementTree as ET
7
8
from typing import TYPE_CHECKING
8
9
16
17
from pymatgen .symmetry .bandstructure import HighSymmKpath
17
18
18
19
if TYPE_CHECKING :
19
- from pathlib import Path
20
+ from typing import ClassVar , Literal
20
21
22
+ from numpy .typing import ArrayLike , NDArray
21
23
from typing_extensions import Self
22
24
25
+ from pymatgen .util .typing import PathLike
26
+
23
27
__author__ = "Christian Vorwerk"
24
28
__copyright__ = "Copyright 2016"
25
29
__version__ = "1.0"
@@ -37,10 +41,18 @@ class ExcitingInput(MSONable):
37
41
Attributes:
38
42
structure (Structure): Associated Structure.
39
43
title (str): Optional title string.
40
- lockxyz (numpy.ndarray ): Lockxyz attribute for each site if available. A Nx3 array of booleans .
44
+ lockxyz (Nx3 NDArray of booleans ): Lockxyz attribute for each site if available.
41
45
"""
42
46
43
- def __init__ (self , structure : Structure , title = None , lockxyz = None ):
47
+ # Conversion factor between Bohr radius and Angstrom
48
+ bohr2ang : ClassVar [float ] = const .value ("Bohr radius" ) / const .value ("Angstrom star" )
49
+
50
+ def __init__ (
51
+ self ,
52
+ structure : Structure ,
53
+ title : str | None = None ,
54
+ lockxyz : ArrayLike | None = None ,
55
+ ) -> None :
44
56
"""
45
57
Args:
46
58
structure (Structure): Structure object.
@@ -52,22 +64,19 @@ def __init__(self, structure: Structure, title=None, lockxyz=None):
52
64
if structure .is_ordered :
53
65
site_properties = {}
54
66
if lockxyz :
55
- site_properties ["selective_dynamics" ] = lockxyz
67
+ site_properties ["selective_dynamics" ] = np . asarray ( lockxyz )
56
68
self .structure = structure .copy (site_properties = site_properties )
57
69
self .title = structure .formula if title is None else title
58
70
else :
59
71
raise ValueError ("Structure with partial occupancies cannot be converted into exciting input!" )
60
72
61
- # define conversion factor between Bohr radius and Angstrom
62
- bohr2ang = const .value ("Bohr radius" ) / const .value ("Angstrom star" )
63
-
64
73
@property
65
- def lockxyz (self ):
74
+ def lockxyz (self ) -> NDArray :
66
75
"""Selective dynamics site properties."""
67
76
return self .structure .site_properties .get ("selective_dynamics" )
68
77
69
78
@lockxyz .setter
70
- def lockxyz (self , lockxyz ) :
79
+ def lockxyz (self , lockxyz : ArrayLike ) -> NDArray :
71
80
self .structure .add_site_property ("selective_dynamics" , lockxyz )
72
81
73
82
@classmethod
@@ -164,7 +173,7 @@ def from_str(cls, data: str) -> Self:
164
173
return cls (structure_in , title_in , lockxyz )
165
174
166
175
@classmethod
167
- def from_file (cls , filename : str | Path ) -> Self :
176
+ def from_file (cls , filename : PathLike ) -> Self :
168
177
"""
169
178
Args:
170
179
filename: Filename.
@@ -178,32 +187,27 @@ def from_file(cls, filename: str | Path) -> Self:
178
187
179
188
def write_etree (
180
189
self ,
181
- celltype ,
182
- cartesian = False ,
183
- bandstr = False ,
190
+ celltype : Literal [ "unchanged" , "conventional" , "primitive" ] ,
191
+ cartesian : bool = False ,
192
+ bandstr : bool = False ,
184
193
symprec : float = 0.4 ,
185
- angle_tolerance = 5 ,
194
+ angle_tolerance : float = 5 ,
186
195
** kwargs ,
187
- ):
188
- """Write the exciting input parameters to an XML object.
196
+ ) -> ET . Element :
197
+ """Convert the exciting input parameters to an XML Element object.
189
198
190
199
Args:
191
200
celltype (str): Choice of unit cell. Can be either the unit cell
192
201
from self.structure ("unchanged"), the conventional cell
193
202
("conventional"), or the primitive unit cell ("primitive").
194
-
195
203
cartesian (bool): Whether the atomic positions are provided in
196
204
Cartesian or unit-cell coordinates. Default is False.
197
-
198
205
bandstr (bool): Whether the bandstructure path along the
199
206
HighSymmKpath is included in the input file. Only supported if the
200
207
celltype is set to "primitive". Default is False.
201
-
202
208
symprec (float): Tolerance for the symmetry finding. Default is 0.4.
203
-
204
209
angle_tolerance (float): Angle tolerance for the symmetry finding.
205
- Default is 5.
206
-
210
+ Default is 5.
207
211
**kwargs: Additional parameters for the input file.
208
212
209
213
Returns:
@@ -297,77 +301,68 @@ def write_etree(
297
301
298
302
def write_string (
299
303
self ,
300
- celltype ,
301
- cartesian = False ,
302
- bandstr = False ,
304
+ celltype : Literal [ "unchanged" , "conventional" , "primitive" ] ,
305
+ cartesian : bool = False ,
306
+ bandstr : bool = False ,
303
307
symprec : float = 0.4 ,
304
- angle_tolerance = 5 ,
308
+ angle_tolerance : float = 5 ,
305
309
** kwargs ,
306
- ):
307
- """Write exciting input.xml as a string.
310
+ ) -> str :
311
+ """Convert exciting input to a string.
308
312
309
313
Args:
310
314
celltype (str): Choice of unit cell. Can be either the unit cell
311
- from self.structure ("unchanged"), the conventional cell
312
- ("conventional"), or the primitive unit cell ("primitive").
313
-
315
+ from self.structure ("unchanged"), the conventional cell
316
+ ("conventional"), or the primitive unit cell ("primitive").
314
317
cartesian (bool): Whether the atomic positions are provided in
315
- Cartesian or unit-cell coordinates. Default is False.
316
-
318
+ Cartesian or unit-cell coordinates. Default is False.
317
319
bandstr (bool): Whether the bandstructure path along the
318
- HighSymmKpath is included in the input file. Only supported if the
319
- celltype is set to "primitive". Default is False.
320
-
320
+ HighSymmKpath is included in the input file. Only supported if the
321
+ celltype is set to "primitive". Default is False.
321
322
symprec (float): Tolerance for the symmetry finding. Default is 0.4.
322
-
323
323
angle_tolerance (float): Angle tolerance for the symmetry finding.
324
- Default is 5.
325
-
324
+ Default is 5.
326
325
**kwargs: Additional parameters for the input file.
327
326
328
327
Returns:
329
- String
328
+ str
330
329
"""
331
330
try :
332
331
root = self .write_etree (celltype , cartesian , bandstr , symprec , angle_tolerance , ** kwargs )
333
332
self ._indent (root )
334
333
# output should be a string not a bytes object
335
334
string = ET .tostring (root ).decode ("UTF-8" )
335
+
336
336
except Exception :
337
337
raise ValueError ("Incorrect celltype!" )
338
+
338
339
return string
339
340
340
341
def write_file (
341
342
self ,
342
- celltype ,
343
- filename ,
344
- cartesian = False ,
345
- bandstr = False ,
343
+ celltype : Literal [ "unchanged" , "conventional" , "primitive" ] ,
344
+ filename : PathLike ,
345
+ cartesian : bool = False ,
346
+ bandstr : bool = False ,
346
347
symprec : float = 0.4 ,
347
- angle_tolerance = 5 ,
348
+ angle_tolerance : float = 5 ,
348
349
** kwargs ,
349
- ):
350
+ ) -> None :
350
351
"""Write exciting input file.
351
352
352
353
Args:
353
354
celltype (str): Choice of unit cell. Can be either the unit cell
354
- from self.structure ("unchanged"), the conventional cell
355
- ("conventional"), or the primitive unit cell ("primitive").
356
-
357
- filename (str): Filename for exciting input.
358
-
355
+ from self.structure ("unchanged"), the conventional cell
356
+ ("conventional"), or the primitive unit cell ("primitive").
357
+ filename (PathLike): Filename for exciting input.
359
358
cartesian (bool): Whether the atomic positions are provided in
360
- Cartesian or unit-cell coordinates. Default is False.
361
-
359
+ Cartesian or unit-cell coordinates. Default is False.
362
360
bandstr (bool): Whether the bandstructure path along the
363
- HighSymmKpath is included in the input file. Only supported if the
364
- celltype is set to "primitive". Default is False.
365
-
361
+ HighSymmKpath is included in the input file. Only supported if the
362
+ celltype is set to "primitive". Default is False.
366
363
symprec (float): Tolerance for the symmetry finding. Default is 0.4.
367
-
368
364
angle_tolerance (float): Angle tolerance for the symmetry finding.
369
- Default is 5.
370
-
365
+ Default is 5.
371
366
**kwargs: Additional parameters for the input file.
372
367
"""
373
368
try :
@@ -378,15 +373,15 @@ def write_file(
378
373
except Exception :
379
374
raise ValueError ("Incorrect celltype!" )
380
375
381
- # Missing PrettyPrint option in the current version of xml.etree.cElementTree
382
376
@staticmethod
383
- def _indent (elem , level = 0 ) :
377
+ def _indent (elem : ET . Element , level : int = 0 ) -> None :
384
378
"""
385
- Helper method to indent elements.
379
+ Helper method to indent elements, as missing PrettyPrint option
380
+ in the current version of xml.etree.cElementTree.
386
381
387
382
Args:
388
- elem:
389
- level:
383
+ elem (ET.Element): The Element to process.
384
+ level (int): The indentation level.
390
385
"""
391
386
i = "\n " + level * " "
392
387
if len (elem ):
@@ -401,19 +396,23 @@ def _indent(elem, level=0):
401
396
elif level and (not elem .tail or not elem .tail .strip ()):
402
397
elem .tail = i
403
398
404
- def _dicttoxml (self , paramdict_ , element ) :
399
+ def _dicttoxml (self , paramdict_ : dict , element : ET . Element ) -> None :
405
400
for key , value in paramdict_ .items ():
406
401
if isinstance (value , str ) and key == "text()" :
407
402
element .text = value
403
+
408
404
elif isinstance (value , str ):
409
405
element .attrib [key ] = value
406
+
410
407
elif isinstance (value , list ):
411
408
for item in value :
412
409
self ._dicttoxml (item , ET .SubElement (element , key ))
410
+
413
411
elif isinstance (value , dict ):
414
412
if element .findall (key ) == []:
415
413
self ._dicttoxml (value , ET .SubElement (element , key ))
416
414
else :
417
415
self ._dicttoxml (value , element .findall (key )[0 ])
416
+
418
417
else :
419
- print ( "cannot deal with" , key , "= " , value )
418
+ warnings . warn ( f "cannot deal with { key } = { value } " , stacklevel = 2 )
0 commit comments