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

1import sys 

2import io 

3from ase.io import read 

4from ase.cli.main import CLIError 

5 

6template_help = """ 

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

8expects the comma separated list of the fields to include 

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

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

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

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

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

14ranked to get ascending ranks). 

15 

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

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

18 

19possible fields: 

20 

21* i: index 

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

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

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

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

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

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

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

29* an: atomic number 

30* el: atomic element 

31* t: atom tag 

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

33 

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

35 

36 

37class CLICommand: 

38 """Print differences between atoms/calculations. 

39 

40 Supports taking differences between different calculation runs of 

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

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

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

44 and image 2 are also provided. 

45 

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

47 customization requires changing the input arguments to the Table 

48 initialization and/or editing the templates file. 

49 """ 

50 

51 @staticmethod 

52 def add_arguments(parser): 

53 add = parser.add_argument 

54 add('file', 

55 help="""Possible file entries are 

56 

57 * 2 non-trajectory files: difference between them 

58 * 1 trajectory file: difference between consecutive images 

59 * 2 trajectory files: difference between corresponding image numbers 

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

61 

62 Note deltas are defined as 2 - 1. 

63  

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

65 """, 

66 nargs='+') 

67 add('-r', 

68 '--rank-order', 

69 metavar='FIELD', 

70 nargs='?', 

71 const='d', 

72 type=str, 

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

74fields. 

75 

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

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

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

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

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

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

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

83 ", useful if rank ordering") 

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

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

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

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

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

89 help="""Specify the summary functions. 

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

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

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

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

94 help="output table in csv format") 

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

96 default=2, type=int, 

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

98 

99 @staticmethod 

100 def run(args, parser): 

101 if args.template_help: 

102 print(template_help) 

103 return 

104 

105 encoding = 'utf-8' 

106 

107 if args.log_file is None: 

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

109 else: 

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

111 

112 with out: 

113 CLICommand.diff(args, out) 

114 

115 @staticmethod 

116 def diff(args, out): 

117 from ase.cli.template import ( 

118 Table, 

119 TableFormat, 

120 slice_split, 

121 field_specs_on_conditions, 

122 summary_functions_on_conditions, 

123 rmsd, 

124 energy_delta) 

125 

126 if args.template is None: 

127 field_specs = field_specs_on_conditions( 

128 args.calculator_outputs, args.rank_order) 

129 else: 

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

131 if not args.calculator_outputs: 

132 for field_spec in field_specs: 

133 if 'f' in field_spec: 

134 raise CLIError( 

135 "field requiring calculation outputs " 

136 "without --calculator-outputs") 

137 

138 if args.summary_functions is None: 

139 summary_functions = summary_functions_on_conditions( 

140 args.calculator_outputs) 

141 else: 

142 summary_functions_dct = { 

143 'rmsd': rmsd, 

144 'dE': energy_delta} 

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

146 if not args.calculator_outputs: 

147 for sf in summary_functions: 

148 if sf == 'dE': 

149 raise CLIError( 

150 "summary function requiring calculation outputs " 

151 "without --calculator-outputs") 

152 summary_functions = [summary_functions_dct[i] 

153 for i in summary_functions] 

154 

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

156 file1 = args.file[0] 

157 actual_filename, index = slice_split(file1) 

158 atoms1 = read(actual_filename, index) 

159 natoms1 = len(atoms1) 

160 

161 if have_two_files: 

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

163 atoms2 = atoms1 

164 

165 def header_fmt(c): 

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

167 else: 

168 file2 = args.file[1] 

169 actual_filename, index = slice_split(file2) 

170 atoms2 = read(actual_filename, index) 

171 natoms2 = len(atoms2) 

172 

173 same_length = natoms1 == natoms2 

174 one_l_one = natoms1 == 1 or natoms2 == 1 

175 

176 if not same_length and not one_l_one: 

177 raise CLIError( 

178 "Trajectory files are not the same length " 

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

180 natoms1, natoms2)) 

181 elif not same_length and one_l_one: 

182 print( 

183 "One file contains one image " 

184 "and the other multiple images,\n" 

185 "assuming you want to compare all images " 

186 "with one reference image") 

187 if natoms1 > natoms2: 

188 atoms2 = natoms1 * atoms2 

189 else: 

190 atoms1 = natoms2 * atoms1 

191 

192 def header_fmt(c): 

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

194 else: 

195 def header_fmt(c): 

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

197 else: 

198 atoms2 = atoms1.copy() 

199 atoms1 = atoms1[:-1] 

200 atoms2 = atoms2[1:] 

201 natoms2 = natoms1 = natoms1 - 1 

202 

203 def header_fmt(c): 

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

205 

206 natoms = natoms1 # = natoms2 

207 

208 output = '' 

209 tableformat = TableFormat(precision=args.precision, 

210 columnwidth=7 + args.precision) 

211 

212 table = Table( 

213 field_specs, 

214 max_lines=args.max_lines, 

215 tableformat=tableformat, 

216 summary_functions=summary_functions) 

217 

218 for counter in range(natoms): 

219 table.title = header_fmt(counter) 

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

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

222 print(output, file=out)