Coverage for sparc/sparc_parsers/aimd.py: 90%

106 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-18 16:19 +0000

1""" 

2Created on Thu Oct 18 14:16:21 2018 

3 

4Ben Comer (Georgia Tech) 

5 

6This file has been heavily modified since SPARC 0.1 

7 

8TODO: more descriptions about this file io parser 

9""" 

10import re 

11from warnings import warn 

12 

13import numpy as np 

14from ase.units import AUT, Angstrom, Bohr, GPa, Hartree, fs 

15 

16# Safe wrappers for both string and fd 

17from ase.utils import reader, writer 

18 

19from ..api import SparcAPI 

20from .utils import strip_comments 

21 

22 

23@reader 

24def _read_aimd(fileobj): 

25 """Parse the aimd information Each geopt is similar to the static 

26 block, except that the field name is started by ':' The 

27 relaxations are separated by ':MDSTEP:' seperators 

28 

29 """ 

30 contents = fileobj.read() 

31 # label = get_label(fileobj, ".ion") 

32 # The geopt comments are simply discarded 

33 stripped, comments = strip_comments(contents) 

34 # Do not include the description lines 

35 data = [line for line in stripped if ":Desc" not in line] 

36 

37 # find the index for all atom type lines. They should be at the 

38 # top of their block 

39 step_bounds = [i for i, x in enumerate(data) if ":MDSTEP:" in x] + [len(data)] 

40 raw_aimd_blocks = [ 

41 data[start:end] for start, end in zip(step_bounds[:-1], step_bounds[1:]) 

42 ] 

43 aimd_steps = [_read_aimd_step(step) for step in raw_aimd_blocks] 

44 

45 return {"aimd": aimd_steps} 

46 

47 

48def _read_aimd_step(raw_aimd_text): 

49 """Parse a geopt step and compose the data dict 

50 

51 Arguments 

52 raw_aimd_text: list of lines within the step 

53 

54 Most values are just presented in their output format, 

55 higher level function calling _read_aimd_step and _read_aimd 

56 should implement how to use the values, 

57 e.g. E_tot = E_tot_per_atom * N_atoms 

58 

59 """ 

60 header_pattern = re.compile(r"^:\w+:") 

61 header, body = raw_aimd_text[0], raw_aimd_text[1:] 

62 if ":MDSTEP:" not in header: 

63 raise ValueError("Wrong aimd format! The :MDSTEP: label is missing.") 

64 # Geopt file uses 1-indexed step names, convert to 0-indexed 

65 step = int(header.split(":MDSTEP:")[-1]) - 1 

66 print("Step ", step) 

67 bounds = [i for i, x in enumerate(body) if header_pattern.match(x)] + [len(body)] 

68 blocks = [body[start:end] for start, end in zip(bounds[:-1], bounds[1:])] 

69 data = {} 

70 for block in blocks: 

71 header_block, body_block = block[0], block[1:] 

72 header_name = header_block.split(":")[1] 

73 header_data = header_block.split(":")[-1].strip() 

74 if len(header_data) > 0: 

75 block_raw_data = [header_data] + body_block 

76 else: 

77 block_raw_data = body_block 

78 # import pdb; pdb.set_trace() 

79 raw_value = np.genfromtxt(block_raw_data, dtype=float) 

80 # The type definitions from MD may be treated from API again? 

81 if header_name == "R": 

82 name = "positions" 

83 value = raw_value.reshape((-1, 3)) * Bohr 

84 elif header_name == "V": 

85 name = "velocities" 

86 value = raw_value.reshape((-1, 3)) * Bohr / AUT 

87 elif header_name == "F": 

88 name = "forces" 

89 value = raw_value.reshape((-1, 3)) * Hartree / Bohr 

90 elif header_name == "MDTM": 

91 # This is not the md integration time! 

92 name = "md_walltime" 

93 value = float(raw_value) 

94 elif header_name == "TEL": 

95 name = "electron temp" 

96 value = float(raw_value) 

97 elif header_name == "TIO": 

98 name = "ion temp" 

99 value = float(raw_value) 

100 elif header_name == "TEN": 

101 # Note it's the total energy per atom! 

102 # TODO: shall we convert to ase fashion? 

103 name = "total energy per atom" 

104 value = float(raw_value) * Hartree 

105 elif header_name == "KEN": 

106 # Note it's the total energy per atom! 

107 # TODO: shall we convert to ase fashion? 

108 name = "kinetic energy per atom" 

109 value = float(raw_value) * Hartree 

110 elif header_name == "KENIG": 

111 # Note it's the total energy per atom! 

112 # TODO: shall we convert to ase fashion? 

113 name = "kinetic energy (ideal gas) per atom" 

114 value = float(raw_value) * Hartree 

115 elif header_name == "FEN": 

116 # Note it's the total energy per atom! 

117 # TODO: shall we convert to ase fashion? 

118 name = "free energy per atom" 

119 value = float(raw_value) * Hartree 

120 elif header_name == "UEN": 

121 # Note it's the total energy per atom! 

122 # TODO: shall we convert to ase fashion? 

123 name = "internal energy per atom" 

124 value = float(raw_value) * Hartree 

125 elif header_name == "TSEN": 

126 # Note it's the total energy per atom! 

127 # TODO: shall we convert to ase fashion? 

128 name = "entropy*T per atom" 

129 value = float(raw_value) * Hartree 

130 elif header_name == "STRESS": 

131 # Same rule as STRESS in geopt 

132 # no conversion to Voigt form yet 

133 dim = np.atleast_1d(raw_value).shape[0] 

134 if dim == 3: 

135 name = "stress" 

136 value = raw_value * GPa 

137 elif dim == 2: 

138 name = "stress_2d" 

139 value = raw_value * Hartree / Bohr**2 

140 elif dim == 1: 

141 name = "stress_1d" 

142 value = float(raw_value * Hartree / Bohr) 

143 else: 

144 raise ValueError("Incorrect stress matrix dimension!") 

145 elif header_name == "STRIO": 

146 # Don't do the volume conversion now 

147 name = "stress (ion-kinetic)" 

148 value = raw_value * GPa 

149 elif header_name == "PRES": 

150 # Don't do the volume conversion now 

151 name = "pressure" 

152 value = raw_value * GPa 

153 elif header_name == "PRESIO": 

154 # Don't do the volume conversion now 

155 name = "pressure (ion-kinetic)" 

156 value = raw_value * GPa 

157 elif header_name == "PRESIG": 

158 # Don't do the volume conversion now 

159 name = "pressure (ideal gas)" 

160 value = raw_value * GPa 

161 elif header_name in ("AVGV", "MAXV", "MIND", "TWIST"): 

162 warn(f"MD output keyword {header_name} will not be parsed.") 

163 value = None 

164 else: 

165 warn(f"MD output keyword {header_name} not known to SPARC. " "Ignore.") 

166 value = None 

167 if value is not None: 

168 data[name] = value 

169 data["step"] = step 

170 return data 

171 

172 

173@writer 

174def _write_aimd( 

175 fileobj, 

176 data_dict, 

177): 

178 raise NotImplementedError("Writing aimd file from SPARC-X-API " "not supported!")