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