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