Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# Note: 

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

3# import time during CLI execution 

4import sys 

5from ase.cli.main import CLIError 

6 

7template_help = """ 

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

9expects the comma separated list of the fields to include 

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

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

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

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

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

15ranked to get ascending ranks). 

16 

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

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

19 

20possible fields: 

21 

22* i: index 

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

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

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

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

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

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

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

30* an: atomic number 

31* el: atomic element 

32* t: atom tag 

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

34 

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

36 

37 

38class CLICommand: 

39 """Print differences between atoms/calculations. 

40 

41 Supports taking differences between different calculation runs of 

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

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

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

45 and image 2 are also provided. 

46 

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

48 customization requires changing the input arguments to the Table 

49 initialization and/or editing the templates file. 

50 """ 

51 

52 @staticmethod 

53 def add_arguments(parser): 

54 add = parser.add_argument 

55 add('file', 

56 help="""Possible file entries are 

57 

58 * 2 non-trajectory files: difference between them 

59 * 1 trajectory file: difference between consecutive images 

60 * 2 trajectory files: difference between corresponding image numbers 

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

62 

63 Note deltas are defined as 2 - 1. 

64 

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

66 """, 

67 nargs='+') 

68 add('-r', 

69 '--rank-order', 

70 metavar='FIELD', 

71 nargs='?', 

72 const='d', 

73 type=str, 

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

75fields. 

76 

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

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

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

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

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

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

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

84 ", useful if rank ordering") 

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

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

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

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

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

90 help="""Specify the summary functions. 

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

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

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

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

95 help="output table in csv format") 

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

97 default=2, type=int, 

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

99 

100 @staticmethod 

101 def run(args, parser): 

102 import io 

103 

104 if args.template_help: 

105 print(template_help) 

106 return 

107 

108 encoding = 'utf-8' 

109 

110 if args.log_file is None: 

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

112 else: 

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

114 

115 with out: 

116 CLICommand.diff(args, out) 

117 

118 @staticmethod 

119 def diff(args, out): 

120 from ase.cli.template import ( 

121 Table, 

122 TableFormat, 

123 slice_split, 

124 field_specs_on_conditions, 

125 summary_functions_on_conditions, 

126 rmsd, 

127 energy_delta) 

128 from ase.io import read 

129 

130 if args.template is None: 

131 field_specs = field_specs_on_conditions( 

132 args.calculator_outputs, args.rank_order) 

133 else: 

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

135 if not args.calculator_outputs: 

136 for field_spec in field_specs: 

137 if 'f' in field_spec: 

138 raise CLIError( 

139 "field requiring calculation outputs " 

140 "without --calculator-outputs") 

141 

142 if args.summary_functions is None: 

143 summary_functions = summary_functions_on_conditions( 

144 args.calculator_outputs) 

145 else: 

146 summary_functions_dct = { 

147 'rmsd': rmsd, 

148 'dE': energy_delta} 

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

150 if not args.calculator_outputs: 

151 for sf in summary_functions: 

152 if sf == 'dE': 

153 raise CLIError( 

154 "summary function requiring calculation outputs " 

155 "without --calculator-outputs") 

156 summary_functions = [summary_functions_dct[i] 

157 for i in summary_functions] 

158 

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

160 file1 = args.file[0] 

161 actual_filename, index = slice_split(file1) 

162 atoms1 = read(actual_filename, index) 

163 natoms1 = len(atoms1) 

164 

165 if have_two_files: 

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

167 atoms2 = atoms1 

168 

169 def header_fmt(c): 

170 return 'image # {}'.format(c) 

171 else: 

172 file2 = args.file[1] 

173 actual_filename, index = slice_split(file2) 

174 atoms2 = read(actual_filename, index) 

175 natoms2 = len(atoms2) 

176 

177 same_length = natoms1 == natoms2 

178 one_l_one = natoms1 == 1 or natoms2 == 1 

179 

180 if not same_length and not one_l_one: 

181 raise CLIError( 

182 "Trajectory files are not the same length " 

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

184 natoms1, natoms2)) 

185 elif not same_length and one_l_one: 

186 print( 

187 "One file contains one image " 

188 "and the other multiple images,\n" 

189 "assuming you want to compare all images " 

190 "with one reference image") 

191 if natoms1 > natoms2: 

192 atoms2 = natoms1 * atoms2 

193 else: 

194 atoms1 = natoms2 * atoms1 

195 

196 def header_fmt(c): 

197 return 'sys-ref image # {}'.format(c) 

198 else: 

199 def header_fmt(c): 

200 return 'sys2-sys1 image # {}'.format(c) 

201 else: 

202 atoms2 = atoms1.copy() 

203 atoms1 = atoms1[:-1] 

204 atoms2 = atoms2[1:] 

205 natoms2 = natoms1 = natoms1 - 1 

206 

207 def header_fmt(c): 

208 return 'images {}-{}'.format(c + 1, c) 

209 

210 natoms = natoms1 # = natoms2 

211 

212 output = '' 

213 tableformat = TableFormat(precision=args.precision, 

214 columnwidth=7 + args.precision) 

215 

216 table = Table( 

217 field_specs, 

218 max_lines=args.max_lines, 

219 tableformat=tableformat, 

220 summary_functions=summary_functions) 

221 

222 for counter in range(natoms): 

223 table.title = header_fmt(counter) 

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

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

226 print(output, file=out)