Coverage for /builds/kinetik161/ase/ase/gui/status.py: 71.13%
97 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-12-10 11:04 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-12-10 11:04 +0000
1from math import acos, pi, sqrt
3import numpy as np
5from ase.data import atomic_names as names
6from ase.data import chemical_symbols as symbols
7from ase.gui.i18n import _
8from ase.gui.utils import get_magmoms
9import warnings
12def formula(Z):
13 hist = {}
14 for z in Z:
15 if z in hist:
16 hist[z] += 1
17 else:
18 hist[z] = 1
19 Z = sorted(hist.keys())
20 strings = []
21 for z in Z:
22 n = hist[z]
23 s = ('' if n == 1 else str(n)) + symbols[z]
24 strings.append(s)
25 return '+'.join(strings)
28class Status: # Status is used as a mixin in GUI
29 def __init__(self):
30 self.ordered_indices = []
32 def status(self, atoms):
33 # use where here: XXX
34 natoms = len(atoms)
35 indices = np.arange(natoms)[self.images.selected[:natoms]]
36 ordered_indices = [i for i in self.images.selected_ordered
37 if i < len(atoms)]
38 n = len(indices)
40 if n == 0:
41 line = ''
42 if atoms.calc:
43 calc = atoms.calc
45 def getresult(name, get_quantity):
46 # ase/io/trajectory.py line 170 does this by using
47 # the get_property(prop, atoms, allow_calculation=False)
48 # so that is an alternative option.
49 try:
50 if calc.calculation_required(atoms, [name]):
51 quantity = None
52 else:
53 quantity = get_quantity()
54 except Exception as err:
55 quantity = None
56 errmsg = ('An error occurred while retrieving {} '
57 'from the calculator: {}'.format(name, err))
58 warnings.warn(errmsg)
59 return quantity
61 energy = getresult('energy', atoms.get_potential_energy)
62 forces = getresult('forces', atoms.get_forces)
64 if energy is not None:
65 line += f'Energy = {energy:.3f} eV'
67 if forces is not None:
68 maxf = np.linalg.norm(forces, axis=1).max()
69 line += f' Max force = {maxf:.3f} eV/Å'
70 self.window.update_status_line(line)
71 return
73 Z = atoms.numbers[indices]
74 R = atoms.positions[indices]
76 if n == 1:
77 tag = atoms.get_tags()[indices[0]]
78 text = (' #%d %s (%s): %.3f Å, %.3f Å, %.3f Å ' %
79 ((indices[0], names[Z[0]], symbols[Z[0]]) + tuple(R[0])))
80 text += _(' tag=%(tag)s') % dict(tag=tag)
81 magmoms = get_magmoms(self.atoms)
82 if magmoms.any():
83 # TRANSLATORS: mom refers to magnetic moment
84 text += _(' mom={:1.2f}'.format(
85 magmoms[indices][0]))
86 charges = self.atoms.get_initial_charges()
87 if charges.any():
88 text += _(' q={:1.2f}'.format(
89 charges[indices][0]))
90 haveit = ['numbers', 'positions', 'forces', 'momenta',
91 'initial_charges', 'initial_magmoms']
92 for key in atoms.arrays:
93 if key not in haveit:
94 val = atoms.get_array(key)[indices[0]]
95 if val is not None:
96 if isinstance(val, int):
97 text += f' {key}={val:g}'
98 else:
99 text += f' {key}={val}'
100 elif n == 2:
101 D = R[0] - R[1]
102 d = sqrt(np.dot(D, D))
103 text = f' {symbols[Z[0]]}-{symbols[Z[1]]}: {d:.3f} Å'
104 elif n == 3:
105 d = []
106 for c in range(3):
107 D = R[c] - R[(c + 1) % 3]
108 d.append(np.dot(D, D))
109 a = []
110 for c in range(3):
111 t1 = 0.5 * (d[c] + d[(c + 1) % 3] - d[(c + 2) % 3])
112 t2 = sqrt(d[c] * d[(c + 1) % 3])
113 try:
114 t3 = acos(t1 / t2)
115 except ValueError:
116 if t1 > 0:
117 t3 = 0
118 else:
119 t3 = pi
120 a.append(t3 * 180 / pi)
121 text = (' %s-%s-%s: %.1f°, %.1f°, %.1f°' %
122 tuple([symbols[z] for z in Z] + a))
123 elif len(ordered_indices) == 4:
124 angle = self.atoms.get_dihedral(*ordered_indices, mic=True)
125 text = ('%s %s → %s → %s → %s: %.1f°' %
126 tuple([_('dihedral')] + [symbols[z] for z in Z] + [angle]))
127 else:
128 text = ' ' + formula(Z)
130 self.window.update_status_line(text)