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
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).
16* example: ase diff start.cif stop.cif --template
17* i:0:1,el,dx,dy,dz,d,rd
19possible fields:
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
34It is possible to change formatters in the template file."""
37class CLICommand:
38 """Print differences between atoms/calculations.
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.
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 """
51 @staticmethod
52 def add_arguments(parser):
53 add = parser.add_argument
54 add('file',
55 help="""Possible file entries are
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
62 Note deltas are defined as 2 - 1.
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.
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")
99 @staticmethod
100 def run(args, parser):
101 if args.template_help:
102 print(template_help)
103 return
105 encoding = 'utf-8'
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)
112 with out:
113 CLICommand.diff(args, out)
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)
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")
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]
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)
161 if have_two_files:
162 if args.file[1] == '-':
163 atoms2 = atoms1
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)
173 same_length = natoms1 == natoms2
174 one_l_one = natoms1 == 1 or natoms2 == 1
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
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
203 def header_fmt(c):
204 return 'images {}-{}'.format(c + 1, c)
206 natoms = natoms1 # = natoms2
208 output = ''
209 tableformat = TableFormat(precision=args.precision,
210 columnwidth=7 + args.precision)
212 table = Table(
213 field_specs,
214 max_lines=args.max_lines,
215 tableformat=tableformat,
216 summary_functions=summary_functions)
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)