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

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
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).
17* example: ase diff start.cif stop.cif --template
18* i:0:1,el,dx,dy,dz,d,rd
20possible fields:
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
35It is possible to change formatters in the template file."""
38class CLICommand:
39 """Print differences between atoms/calculations.
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.
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 """
52 @staticmethod
53 def add_arguments(parser):
54 add = parser.add_argument
55 add('file',
56 help="""Possible file entries are
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
63 Note deltas are defined as 2 - 1.
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.
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")
100 @staticmethod
101 def run(args, parser):
102 import io
104 if args.template_help:
105 print(template_help)
106 return
108 encoding = 'utf-8'
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)
115 with out:
116 CLICommand.diff(args, out)
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
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")
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]
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)
165 if have_two_files:
166 if args.file[1] == '-':
167 atoms2 = atoms1
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)
177 same_length = natoms1 == natoms2
178 one_l_one = natoms1 == 1 or natoms2 == 1
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
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
207 def header_fmt(c):
208 return 'images {}-{}'.format(c + 1, c)
210 natoms = natoms1 # = natoms2
212 output = ''
213 tableformat = TableFormat(precision=args.precision,
214 columnwidth=7 + args.precision)
216 table = Table(
217 field_specs,
218 max_lines=args.max_lines,
219 tableformat=tableformat,
220 summary_functions=summary_functions)
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)