Coverage for /builds/ase/ase/ase/gui/quickinfo.py: 72.15%
79 statements
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +0000
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +0000
1# fmt: off
3"Module for displaying information about the system."
6import warnings
8import numpy as np
10from ase.gui.i18n import _
12ucellformat = """\
13 {:8.3f} {:8.3f} {:8.3f}
14 {:8.3f} {:8.3f} {:8.3f}
15 {:8.3f} {:8.3f} {:8.3f}
16"""
19def info(gui):
20 images = gui.images
21 nimg = len(images)
22 atoms = gui.atoms
24 tokens = []
26 def add(token=''):
27 tokens.append(token)
29 if len(atoms) < 1:
30 add(_('This frame has no atoms.'))
31 else:
32 img = gui.frame
34 if nimg == 1:
35 add(_('Single image loaded.'))
36 else:
37 add(_('Image {} loaded (0–{}).').format(img, nimg - 1))
38 add()
39 add(_('Number of atoms: {}').format(len(atoms)))
41 # We need to write ų further down, so we have no choice but to
42 # use proper subscripts in the chemical formula:
43 formula = atoms.get_chemical_formula()
44 subscripts = dict(zip('0123456789', '₀₁₂₃₄₅₆₇₈₉'))
45 pretty_formula = ''.join(subscripts.get(c, c) for c in formula)
46 add(pretty_formula)
48 add()
49 add(_('Unit cell [Å]:'))
50 add(ucellformat.format(*atoms.cell.ravel()))
51 periodic = [[_('no'), _('yes')][int(periodic)]
52 for periodic in atoms.pbc]
53 # TRANSLATORS: This has the form Periodic: no, no, yes
54 add(_('Periodic: {}, {}, {}').format(*periodic))
55 add()
57 cellpar = atoms.cell.cellpar()
58 add()
59 add(_('Lengths [Å]: {:.3f}, {:.3f}, {:.3f}').format(*cellpar[:3]))
60 add(_('Angles: {:.1f}°, {:.1f}°, {:.1f}°').format(*cellpar[3:]))
62 if atoms.cell.rank == 3:
63 add(_('Volume: {:.3f} ų').format(atoms.cell.volume))
65 add()
67 if nimg > 1:
68 if all((atoms.cell == img.cell).all() for img in images):
69 add(_('Unit cell is fixed.'))
70 else:
71 add(_('Unit cell varies.'))
73 if atoms.pbc[:2].all() and atoms.cell.rank >= 1:
74 try:
75 lat = atoms.cell.get_bravais_lattice()
76 except RuntimeError:
77 add(_('Could not recognize the lattice type'))
78 except Exception:
79 add(_('Unexpected error determining lattice type'))
80 else:
81 add(_('Reduced Bravais lattice:\n{}').format(lat))
83 # Print electronic structure information if we have a calculator
84 if atoms.calc:
85 calc = atoms.calc
87 def getresult(name, get_quantity):
88 # ase/io/trajectory.py line 170 does this by using
89 # the get_property(prop, atoms, allow_calculation=False)
90 # so that is an alternative option.
91 try:
92 if calc.calculation_required(atoms, [name]):
93 quantity = None
94 else:
95 quantity = get_quantity()
96 except Exception as err:
97 quantity = None
98 errmsg = ('An error occurred while retrieving {} '
99 'from the calculator: {}'.format(name, err))
100 warnings.warn(errmsg)
101 return quantity
103 # SinglePointCalculators are named after the code which
104 # produced the result, so this will typically list the
105 # name of a code even if they are just cached results.
106 add()
107 from ase.calculators.singlepoint import SinglePointCalculator
108 if isinstance(calc, SinglePointCalculator):
109 add(_('Calculator: {} (cached)').format(calc.name))
110 else:
111 add(_('Calculator: {} (attached)').format(calc.name))
113 energy = getresult('energy', atoms.get_potential_energy)
114 forces = getresult('forces', atoms.get_forces)
115 magmom = getresult('magmom', atoms.get_magnetic_moment)
117 if energy is not None:
118 energy_str = _('Energy: {:.3f} eV').format(energy)
119 add(energy_str)
121 if forces is not None:
122 maxf = np.linalg.norm(forces, axis=1).max()
123 forces_str = _('Max force: {:.3f} eV/Å').format(maxf)
124 add(forces_str)
126 if magmom is not None:
127 mag_str = _('Magmom: {:.3f} µ').format(magmom)
128 add(mag_str)
130 return '\n'.join(tokens)