Coverage for /builds/kinetik161/ase/ase/gui/quickinfo.py: 72.15%

79 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-12-10 11:04 +0000

1"Module for displaying information about the system." 

2 

3 

4import warnings 

5 

6import numpy as np 

7 

8from ase.gui.i18n import _ 

9 

10ucellformat = """\ 

11 {:8.3f} {:8.3f} {:8.3f} 

12 {:8.3f} {:8.3f} {:8.3f} 

13 {:8.3f} {:8.3f} {:8.3f} 

14""" 

15 

16 

17def info(gui): 

18 images = gui.images 

19 nimg = len(images) 

20 atoms = gui.atoms 

21 

22 tokens = [] 

23 

24 def add(token=''): 

25 tokens.append(token) 

26 

27 if len(atoms) < 1: 

28 add(_('This frame has no atoms.')) 

29 else: 

30 img = gui.frame 

31 

32 if nimg == 1: 

33 add(_('Single image loaded.')) 

34 else: 

35 add(_('Image {} loaded (0–{}).').format(img, nimg - 1)) 

36 add() 

37 add(_('Number of atoms: {}').format(len(atoms))) 

38 

39 # We need to write ų further down, so we have no choice but to 

40 # use proper subscripts in the chemical formula: 

41 formula = atoms.get_chemical_formula() 

42 subscripts = dict(zip('0123456789', '₀₁₂₃₄₅₆₇₈₉')) 

43 pretty_formula = ''.join(subscripts.get(c, c) for c in formula) 

44 add(pretty_formula) 

45 

46 add() 

47 add(_('Unit cell [Å]:')) 

48 add(ucellformat.format(*atoms.cell.ravel())) 

49 periodic = [[_('no'), _('yes')][int(periodic)] 

50 for periodic in atoms.pbc] 

51 # TRANSLATORS: This has the form Periodic: no, no, yes 

52 add(_('Periodic: {}, {}, {}').format(*periodic)) 

53 add() 

54 

55 cellpar = atoms.cell.cellpar() 

56 add() 

57 add(_('Lengths [Å]: {:.3f}, {:.3f}, {:.3f}').format(*cellpar[:3])) 

58 add(_('Angles: {:.1f}°, {:.1f}°, {:.1f}°').format(*cellpar[3:])) 

59 

60 if atoms.cell.rank == 3: 

61 add(_('Volume: {:.3f} ų').format(atoms.cell.volume)) 

62 

63 add() 

64 

65 if nimg > 1: 

66 if all((atoms.cell == img.cell).all() for img in images): 

67 add(_('Unit cell is fixed.')) 

68 else: 

69 add(_('Unit cell varies.')) 

70 

71 if atoms.pbc[:2].all() and atoms.cell.rank >= 1: 

72 try: 

73 lat = atoms.cell.get_bravais_lattice() 

74 except RuntimeError: 

75 add(_('Could not recognize the lattice type')) 

76 except Exception: 

77 add(_('Unexpected error determining lattice type')) 

78 else: 

79 add(_('Reduced Bravais lattice:\n{}').format(lat)) 

80 

81 # Print electronic structure information if we have a calculator 

82 if atoms.calc: 

83 calc = atoms.calc 

84 

85 def getresult(name, get_quantity): 

86 # ase/io/trajectory.py line 170 does this by using 

87 # the get_property(prop, atoms, allow_calculation=False) 

88 # so that is an alternative option. 

89 try: 

90 if calc.calculation_required(atoms, [name]): 

91 quantity = None 

92 else: 

93 quantity = get_quantity() 

94 except Exception as err: 

95 quantity = None 

96 errmsg = ('An error occurred while retrieving {} ' 

97 'from the calculator: {}'.format(name, err)) 

98 warnings.warn(errmsg) 

99 return quantity 

100 

101 # SinglePointCalculators are named after the code which 

102 # produced the result, so this will typically list the 

103 # name of a code even if they are just cached results. 

104 add() 

105 from ase.calculators.singlepoint import SinglePointCalculator 

106 if isinstance(calc, SinglePointCalculator): 

107 add(_('Calculator: {} (cached)').format(calc.name)) 

108 else: 

109 add(_('Calculator: {} (attached)').format(calc.name)) 

110 

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

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

113 magmom = getresult('magmom', atoms.get_magnetic_moment) 

114 

115 if energy is not None: 

116 energy_str = _('Energy: {:.3f} eV').format(energy) 

117 add(energy_str) 

118 

119 if forces is not None: 

120 maxf = np.linalg.norm(forces, axis=1).max() 

121 forces_str = _('Max force: {:.3f} eV/Å').format(maxf) 

122 add(forces_str) 

123 

124 if magmom is not None: 

125 mag_str = _('Magmom: {:.3f} µ').format(magmom) 

126 add(mag_str) 

127 

128 return '\n'.join(tokens)