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

1from math import acos, pi, sqrt 

2 

3import numpy as np 

4 

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 

10 

11 

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) 

26 

27 

28class Status: # Status is used as a mixin in GUI 

29 def __init__(self): 

30 self.ordered_indices = [] 

31 

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) 

39 

40 if n == 0: 

41 line = '' 

42 if atoms.calc: 

43 calc = atoms.calc 

44 

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 

60 

61 energy = getresult('energy', atoms.get_potential_energy) 

62 forces = getresult('forces', atoms.get_forces) 

63 

64 if energy is not None: 

65 line += f'Energy = {energy:.3f} eV' 

66 

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 

72 

73 Z = atoms.numbers[indices] 

74 R = atoms.positions[indices] 

75 

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) 

129 

130 self.window.update_status_line(text)