Coverage for /builds/ase/ase/ase/db/row.py : 92.86%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from random import randint
2from typing import Dict, Any
4import numpy as np
6from ase import Atoms
7from ase.constraints import dict2constraint
8from ase.calculators.calculator import (all_properties,
9 PropertyNotImplementedError,
10 kptdensity2monkhorstpack)
11from ase.calculators.singlepoint import SinglePointCalculator
12from ase.data import chemical_symbols, atomic_masses
13from ase.formula import Formula
14from ase.geometry import cell_to_cellpar
15from ase.io.jsonio import decode
18class FancyDict(dict):
19 """Dictionary with keys available as attributes also."""
20 def __getattr__(self, key):
21 if key not in self:
22 return dict.__getattribute__(self, key)
23 value = self[key]
24 if isinstance(value, dict):
25 return FancyDict(value)
26 return value
28 def __dir__(self):
29 return self.keys() # for tab-completion
32def atoms2dict(atoms):
33 dct = {
34 'numbers': atoms.numbers,
35 'positions': atoms.positions,
36 'unique_id': '%x' % randint(16**31, 16**32 - 1)}
37 if atoms.pbc.any():
38 dct['pbc'] = atoms.pbc
39 if atoms.cell.any():
40 dct['cell'] = atoms.cell
41 if atoms.has('initial_magmoms'):
42 dct['initial_magmoms'] = atoms.get_initial_magnetic_moments()
43 if atoms.has('initial_charges'):
44 dct['initial_charges'] = atoms.get_initial_charges()
45 if atoms.has('masses'):
46 dct['masses'] = atoms.get_masses()
47 if atoms.has('tags'):
48 dct['tags'] = atoms.get_tags()
49 if atoms.has('momenta'):
50 dct['momenta'] = atoms.get_momenta()
51 if atoms.constraints:
52 dct['constraints'] = [c.todict() for c in atoms.constraints]
53 if atoms.calc is not None:
54 dct['calculator'] = atoms.calc.name.lower()
55 dct['calculator_parameters'] = atoms.calc.todict()
56 if len(atoms.calc.check_state(atoms)) == 0:
57 for prop in all_properties:
58 try:
59 x = atoms.calc.get_property(prop, atoms, False)
60 except PropertyNotImplementedError:
61 pass
62 else:
63 if x is not None:
64 dct[prop] = x
65 return dct
68class AtomsRow:
69 mtime: float
70 positions: np.ndarray
71 id: int
73 def __init__(self, dct):
74 if isinstance(dct, dict):
75 dct = dct.copy()
76 if 'calculator_parameters' in dct:
77 # Earlier version of ASE would encode the calculator
78 # parameter dict again and again and again ...
79 while isinstance(dct['calculator_parameters'], str):
80 dct['calculator_parameters'] = decode(
81 dct['calculator_parameters'])
82 else:
83 dct = atoms2dict(dct)
84 assert 'numbers' in dct
85 self._constraints = dct.pop('constraints', [])
86 self._constrained_forces = None
87 self._data = dct.pop('data', {})
88 kvp = dct.pop('key_value_pairs', {})
89 self._keys = list(kvp.keys())
90 self.__dict__.update(kvp)
91 self.__dict__.update(dct)
92 if 'cell' not in dct:
93 self.cell = np.zeros((3, 3))
94 if 'pbc' not in dct:
95 self.pbc = np.zeros(3, bool)
97 def __contains__(self, key):
98 return key in self.__dict__
100 def __iter__(self):
101 return (key for key in self.__dict__ if key[0] != '_')
103 def get(self, key, default=None):
104 """Return value of key if present or default if not."""
105 return getattr(self, key, default)
107 @property
108 def key_value_pairs(self):
109 """Return dict of key-value pairs."""
110 return dict((key, self.get(key)) for key in self._keys)
112 def count_atoms(self):
113 """Count atoms.
115 Return dict mapping chemical symbol strings to number of atoms.
116 """
117 count = {}
118 for symbol in self.symbols:
119 count[symbol] = count.get(symbol, 0) + 1
120 return count
122 def __getitem__(self, key):
123 return getattr(self, key)
125 def __setitem__(self, key, value):
126 setattr(self, key, value)
128 def __str__(self):
129 return '<AtomsRow: formula={0}, keys={1}>'.format(
130 self.formula, ','.join(self._keys))
132 @property
133 def constraints(self):
134 """List of constraints."""
135 if not isinstance(self._constraints, list):
136 # Lazy decoding:
137 cs = decode(self._constraints)
138 self._constraints = []
139 for c in cs:
140 # Convert to new format:
141 name = c.pop('__name__', None)
142 if name:
143 c = {'name': name, 'kwargs': c}
144 if c['name'].startswith('ase'):
145 c['name'] = c['name'].rsplit('.', 1)[1]
146 self._constraints.append(c)
147 return [dict2constraint(d) for d in self._constraints]
149 @property
150 def data(self):
151 """Data dict."""
152 if isinstance(self._data, str):
153 self._data = decode(self._data) # lazy decoding
154 elif isinstance(self._data, bytes):
155 from ase.db.core import bytes_to_object
156 self._data = bytes_to_object(self._data) # lazy decoding
157 return FancyDict(self._data)
159 @property
160 def natoms(self):
161 """Number of atoms."""
162 return len(self.numbers)
164 @property
165 def formula(self):
166 """Chemical formula string."""
167 return Formula('', _tree=[(self.symbols, 1)]).format('metal')
169 @property
170 def symbols(self):
171 """List of chemical symbols."""
172 return [chemical_symbols[Z] for Z in self.numbers]
174 @property
175 def fmax(self):
176 """Maximum atomic force."""
177 forces = self.constrained_forces
178 return (forces**2).sum(1).max()**0.5
180 @property
181 def constrained_forces(self):
182 """Forces after applying constraints."""
183 if self._constrained_forces is not None:
184 return self._constrained_forces
185 forces = self.forces
186 constraints = self.constraints
187 if constraints:
188 forces = forces.copy()
189 atoms = self.toatoms()
190 for constraint in constraints:
191 constraint.adjust_forces(atoms, forces)
193 self._constrained_forces = forces
194 return forces
196 @property
197 def smax(self):
198 """Maximum stress tensor component."""
199 return (self.stress**2).max()**0.5
201 @property
202 def mass(self):
203 """Total mass."""
204 if 'masses' in self:
205 return self.masses.sum()
206 return atomic_masses[self.numbers].sum()
208 @property
209 def volume(self):
210 """Volume of unit cell."""
211 if self.cell is None:
212 return None
213 vol = abs(np.linalg.det(self.cell))
214 if vol == 0.0:
215 raise AttributeError
216 return vol
218 @property
219 def charge(self):
220 """Total charge."""
221 charges = self.get('initial_charges')
222 if charges is None:
223 return 0.0
224 return charges.sum()
226 def toatoms(self,
227 add_additional_information=False):
228 """Create Atoms object."""
229 atoms = Atoms(self.numbers,
230 self.positions,
231 cell=self.cell,
232 pbc=self.pbc,
233 magmoms=self.get('initial_magmoms'),
234 charges=self.get('initial_charges'),
235 tags=self.get('tags'),
236 masses=self.get('masses'),
237 momenta=self.get('momenta'),
238 constraint=self.constraints)
240 results = {}
241 for prop in all_properties:
242 if prop in self:
243 results[prop] = self[prop]
244 if results:
245 atoms.calc = SinglePointCalculator(atoms, **results)
246 atoms.calc.name = self.get('calculator', 'unknown')
248 if add_additional_information:
249 atoms.info = {}
250 atoms.info['unique_id'] = self.unique_id
251 if self._keys:
252 atoms.info['key_value_pairs'] = self.key_value_pairs
253 data = self.get('data')
254 if data:
255 atoms.info['data'] = data
257 return atoms
260def row2dct(row, key_descriptions) -> Dict[str, Any]:
261 """Convert row to dict of things for printing or a web-page."""
263 from ase.db.core import float_to_time_string, now
265 dct = {}
267 atoms = Atoms(cell=row.cell, pbc=row.pbc)
268 dct['size'] = kptdensity2monkhorstpack(atoms,
269 kptdensity=1.8,
270 even=False)
272 dct['cell'] = [['{:.3f}'.format(a) for a in axis] for axis in row.cell]
273 par = ['{:.3f}'.format(x) for x in cell_to_cellpar(row.cell)]
274 dct['lengths'] = par[:3]
275 dct['angles'] = par[3:]
277 stress = row.get('stress')
278 if stress is not None:
279 dct['stress'] = ', '.join('{0:.3f}'.format(s) for s in stress)
281 dct['formula'] = Formula(row.formula).format('abc')
283 dipole = row.get('dipole')
284 if dipole is not None:
285 dct['dipole'] = ', '.join('{0:.3f}'.format(d) for d in dipole)
287 data = row.get('data')
288 if data:
289 dct['data'] = ', '.join(data.keys())
291 constraints = row.get('constraints')
292 if constraints:
293 dct['constraints'] = ', '.join(c.__class__.__name__
294 for c in constraints)
296 keys = ({'id', 'energy', 'fmax', 'smax', 'mass', 'age'} |
297 set(key_descriptions) |
298 set(row.key_value_pairs))
299 dct['table'] = []
301 from ase.db.project import KeyDescription
302 for key in keys:
303 if key == 'age':
304 age = float_to_time_string(now() - row.ctime, True)
305 dct['table'].append(('ctime', 'Age', age))
306 continue
307 value = row.get(key)
308 if value is not None:
309 if isinstance(value, float):
310 value = '{:.3f}'.format(value)
311 elif not isinstance(value, str):
312 value = str(value)
314 nokeydesc = KeyDescription(key, '', '', '')
315 keydesc = key_descriptions.get(key, nokeydesc)
316 unit = keydesc.unit
317 if unit:
318 value += ' ' + unit
319 dct['table'].append((key, keydesc.longdesc, value))
321 return dct