Coverage for /builds/kinetik161/ase/ase/cli/diff.py: 85.56%

90 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-12-10 11:04 +0000

1# Note: 

2# Try to avoid module level import statements here to reduce 

3# import time during CLI execution 

4import sys 

5 

6from ase.cli.main import CLIError 

7 

8template_help = """ 

9Without argument, looks for ~/.ase/template.py. Otherwise, 

10expects the comma separated list of the fields to include 

11in their left-to-right order. Optionally, specify the 

12lexicographical sort hierarchy (0 is outermost sort) and if the 

13sort should be ascending or descending (1 or -1). By default, 

14sorting is descending, which makes sense for most things except 

15index (and rank, but one can just sort by the thing which is 

16ranked to get ascending ranks). 

17 

18* example: ase diff start.cif stop.cif --template 

19* i:0:1,el,dx,dy,dz,d,rd 

20 

21possible fields: 

22 

23* i: index 

24* dx,dy,dz,d: displacement/displacement components 

25* dfx,dfy,dfz,df: difference force/force components 

26* afx,afy,afz,af: average force/force components 

27* p1x,p1y,p1z,p: first image positions/position components 

28* p2x,p2y,p2z,p: second image positions/position components 

29* f1x,f1y,f1z,f: first image forces/force components 

30* f2x,f2y,f2z,f: second image forces/force components 

31* an: atomic number 

32* el: atomic element 

33* t: atom tag 

34* r<col>: the rank of that atom with respect to the column 

35 

36It is possible to change formatters in the template file.""" 

37 

38 

39class CLICommand: 

40 """Print differences between atoms/calculations. 

41 

42 Supports taking differences between different calculation runs of 

43 the same system as well as neighboring geometric images for one 

44 calculation run of a system. As part of a difference table or as a 

45 standalone display table, fields for non-difference quantities of image 1 

46 and image 2 are also provided. 

47 

48 See the --template-help for the formatting exposed in the CLI. More 

49 customization requires changing the input arguments to the Table 

50 initialization and/or editing the templates file. 

51 """ 

52 

53 @staticmethod 

54 def add_arguments(parser): 

55 add = parser.add_argument 

56 add('file', 

57 help="""Possible file entries are 

58 

59 * 2 non-trajectory files: difference between them 

60 * 1 trajectory file: difference between consecutive images 

61 * 2 trajectory files: difference between corresponding image numbers 

62 * 1 trajectory file followed by hyphen-minus (ASCII 45): for display 

63 

64 Note deltas are defined as 2 - 1. 

65 

66 Use [FILE]@[SLICE] to select images. 

67 """, 

68 nargs='+') 

69 add('-r', 

70 '--rank-order', 

71 metavar='FIELD', 

72 nargs='?', 

73 const='d', 

74 type=str, 

75 help="""Order atoms by rank, see --template-help for possible 

76fields. 

77 

78The default value, when specified, is d. When not 

79specified, ordering is the same as that provided by the 

80generator. For hierarchical sorting, see template.""") 

81 add('-c', '--calculator-outputs', action="store_true", 

82 help="display calculator outputs of forces and energy") 

83 add('--max-lines', metavar='N', type=int, 

84 help="show only so many lines (atoms) in each table " 

85 ", useful if rank ordering") 

86 add('-t', '--template', metavar='TEMPLATE', nargs='?', const='rc', 

87 help="""See --template-help for the help on this option.""") 

88 add('--template-help', help="""Prints the help for the template file. 

89 Usage `ase diff - --template-help`""", action="store_true") 

90 add('-s', '--summary-functions', metavar='SUMFUNCS', nargs='?', 

91 help="""Specify the summary functions. 

92 Possible values are `rmsd` and `dE`. 

93 Comma separate more than one summary function.""") 

94 add('--log-file', metavar='LOGFILE', help="print table to file") 

95 add('--as-csv', action="store_true", 

96 help="output table in csv format") 

97 add('--precision', metavar='PREC', 

98 default=2, type=int, 

99 help="precision used in both display and sorting") 

100 

101 @staticmethod 

102 def run(args, parser): 

103 import io 

104 

105 if args.template_help: 

106 print(template_help) 

107 return 

108 

109 encoding = 'utf-8' 

110 

111 if args.log_file is None: 

112 out = io.TextIOWrapper(sys.stdout.buffer, encoding=encoding) 

113 else: 

114 out = open(args.log_file, 'w', encoding=encoding) 

115 

116 with out: 

117 CLICommand.diff(args, out) 

118 

119 @staticmethod 

120 def diff(args, out): 

121 from ase.cli.template import (Table, TableFormat, energy_delta, 

122 field_specs_on_conditions, rmsd, 

123 slice_split, 

124 summary_functions_on_conditions) 

125 from ase.io import read 

126 

127 if args.template is None: 

128 field_specs = field_specs_on_conditions( 

129 args.calculator_outputs, args.rank_order) 

130 else: 

131 field_specs = args.template.split(',') 

132 if not args.calculator_outputs: 

133 for field_spec in field_specs: 

134 if 'f' in field_spec: 

135 raise CLIError( 

136 "field requiring calculation outputs " 

137 "without --calculator-outputs") 

138 

139 if args.summary_functions is None: 

140 summary_functions = summary_functions_on_conditions( 

141 args.calculator_outputs) 

142 else: 

143 summary_functions_dct = { 

144 'rmsd': rmsd, 

145 'dE': energy_delta} 

146 summary_functions = args.summary_functions.split(',') 

147 if not args.calculator_outputs: 

148 for sf in summary_functions: 

149 if sf == 'dE': 

150 raise CLIError( 

151 "summary function requiring calculation outputs " 

152 "without --calculator-outputs") 

153 summary_functions = [summary_functions_dct[i] 

154 for i in summary_functions] 

155 

156 have_two_files = len(args.file) == 2 

157 file1 = args.file[0] 

158 actual_filename, index = slice_split(file1) 

159 atoms1 = read(actual_filename, index) 

160 natoms1 = len(atoms1) 

161 

162 if have_two_files: 

163 if args.file[1] == '-': 

164 atoms2 = atoms1 

165 

166 def header_fmt(c): 

167 return f'image # {c}' 

168 else: 

169 file2 = args.file[1] 

170 actual_filename, index = slice_split(file2) 

171 atoms2 = read(actual_filename, index) 

172 natoms2 = len(atoms2) 

173 

174 same_length = natoms1 == natoms2 

175 one_l_one = natoms1 == 1 or natoms2 == 1 

176 

177 if not same_length and not one_l_one: 

178 raise CLIError( 

179 "Trajectory files are not the same length " 

180 "and both > 1\n{}!={}".format( 

181 natoms1, natoms2)) 

182 elif not same_length and one_l_one: 

183 print( 

184 "One file contains one image " 

185 "and the other multiple images,\n" 

186 "assuming you want to compare all images " 

187 "with one reference image") 

188 if natoms1 > natoms2: 

189 atoms2 = natoms1 * atoms2 

190 else: 

191 atoms1 = natoms2 * atoms1 

192 

193 def header_fmt(c): 

194 return f'sys-ref image # {c}' 

195 else: 

196 def header_fmt(c): 

197 return f'sys2-sys1 image # {c}' 

198 else: 

199 atoms2 = atoms1.copy() 

200 atoms1 = atoms1[:-1] 

201 atoms2 = atoms2[1:] 

202 natoms2 = natoms1 = natoms1 - 1 

203 

204 def header_fmt(c): 

205 return f'images {c + 1}-{c}' 

206 

207 natoms = natoms1 # = natoms2 

208 

209 output = '' 

210 tableformat = TableFormat(precision=args.precision, 

211 columnwidth=7 + args.precision) 

212 

213 table = Table( 

214 field_specs, 

215 max_lines=args.max_lines, 

216 tableformat=tableformat, 

217 summary_functions=summary_functions) 

218 

219 for counter in range(natoms): 

220 table.title = header_fmt(counter) 

221 output += table.make(atoms1[counter], 

222 atoms2[counter], csv=args.as_csv) + '\n' 

223 print(output, file=out)