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, Tuple, Any
4import numpy as np
6from ase import Atoms
7from ase.constraints import dict2constraint
8from ase.calculators.calculator import (get_calculator_class, 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 def __init__(self, dct):
70 if isinstance(dct, dict):
71 dct = dct.copy()
72 if 'calculator_parameters' in dct:
73 # Earlier version of ASE would encode the calculator
74 # parameter dict again and again and again ...
75 while isinstance(dct['calculator_parameters'], str):
76 dct['calculator_parameters'] = decode(
77 dct['calculator_parameters'])
78 else:
79 dct = atoms2dict(dct)
80 assert 'numbers' in dct
81 self._constraints = dct.pop('constraints', [])
82 self._constrained_forces = None
83 self._data = dct.pop('data', {})
84 kvp = dct.pop('key_value_pairs', {})
85 self._keys = list(kvp.keys())
86 self.__dict__.update(kvp)
87 self.__dict__.update(dct)
88 if 'cell' not in dct:
89 self.cell = np.zeros((3, 3))
90 if 'pbc' not in dct:
91 self.pbc = np.zeros(3, bool)
93 def __contains__(self, key):
94 return key in self.__dict__
96 def __iter__(self):
97 return (key for key in self.__dict__ if key[0] != '_')
99 def get(self, key, default=None):
100 """Return value of key if present or default if not."""
101 return getattr(self, key, default)
103 @property
104 def key_value_pairs(self):
105 """Return dict of key-value pairs."""
106 return dict((key, self.get(key)) for key in self._keys)
108 def count_atoms(self):
109 """Count atoms.
111 Return dict mapping chemical symbol strings to number of atoms.
112 """
113 count = {}
114 for symbol in self.symbols:
115 count[symbol] = count.get(symbol, 0) + 1
116 return count
118 def __getitem__(self, key):
119 return getattr(self, key)
121 def __setitem__(self, key, value):
122 setattr(self, key, value)
124 def __str__(self):
125 return '<AtomsRow: formula={0}, keys={1}>'.format(
126 self.formula, ','.join(self._keys))
128 @property
129 def constraints(self):
130 """List of constraints."""
131 if not isinstance(self._constraints, list):
132 # Lazy decoding:
133 cs = decode(self._constraints)
134 self._constraints = []
135 for c in cs:
136 # Convert to new format:
137 name = c.pop('__name__', None)
138 if name:
139 c = {'name': name, 'kwargs': c}
140 if c['name'].startswith('ase'):
141 c['name'] = c['name'].rsplit('.', 1)[1]
142 self._constraints.append(c)
143 return [dict2constraint(d) for d in self._constraints]
145 @property
146 def data(self):
147 """Data dict."""
148 if isinstance(self._data, str):
149 self._data = decode(self._data) # lazy decoding
150 elif isinstance(self._data, bytes):
151 from ase.db.core import bytes_to_object
152 self._data = bytes_to_object(self._data) # lazy decoding
153 return FancyDict(self._data)
155 @property
156 def natoms(self):
157 """Number of atoms."""
158 return len(self.numbers)
160 @property
161 def formula(self):
162 """Chemical formula string."""
163 return Formula('', _tree=[(self.symbols, 1)]).format('metal')
165 @property
166 def symbols(self):
167 """List of chemical symbols."""
168 return [chemical_symbols[Z] for Z in self.numbers]
170 @property
171 def fmax(self):
172 """Maximum atomic force."""
173 forces = self.constrained_forces
174 return (forces**2).sum(1).max()**0.5
176 @property
177 def constrained_forces(self):
178 """Forces after applying constraints."""
179 if self._constrained_forces is not None:
180 return self._constrained_forces
181 forces = self.forces
182 constraints = self.constraints
183 if constraints:
184 forces = forces.copy()
185 atoms = self.toatoms()
186 for constraint in constraints:
187 constraint.adjust_forces(atoms, forces)
189 self._constrained_forces = forces
190 return forces
192 @property
193 def smax(self):
194 """Maximum stress tensor component."""
195 return (self.stress**2).max()**0.5
197 @property
198 def mass(self):
199 """Total mass."""
200 if 'masses' in self:
201 return self.masses.sum()
202 return atomic_masses[self.numbers].sum()
204 @property
205 def volume(self):
206 """Volume of unit cell."""
207 if self.cell is None:
208 return None
209 vol = abs(np.linalg.det(self.cell))
210 if vol == 0.0:
211 raise AttributeError
212 return vol
214 @property
215 def charge(self):
216 """Total charge."""
217 charges = self.get('inital_charges')
218 if charges is None:
219 return 0.0
220 return charges.sum()
222 def toatoms(self, attach_calculator=False,
223 add_additional_information=False):
224 """Create Atoms object."""
225 atoms = Atoms(self.numbers,
226 self.positions,
227 cell=self.cell,
228 pbc=self.pbc,
229 magmoms=self.get('initial_magmoms'),
230 charges=self.get('initial_charges'),
231 tags=self.get('tags'),
232 masses=self.get('masses'),
233 momenta=self.get('momenta'),
234 constraint=self.constraints)
236 if attach_calculator:
237 params = self.get('calculator_parameters', {})
238 atoms.calc = get_calculator_class(self.calculator)(**params)
239 else:
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,
261 key_descriptions: Dict[str, Tuple[str, str, str]] = {}
262 ) -> Dict[str, Any]:
263 """Convert row to dict of things for printing or a web-page."""
265 from ase.db.core import float_to_time_string, now
267 dct = {}
269 atoms = Atoms(cell=row.cell, pbc=row.pbc)
270 dct['size'] = kptdensity2monkhorstpack(atoms,
271 kptdensity=1.8,
272 even=False)
274 dct['cell'] = [['{:.3f}'.format(a) for a in axis] for axis in row.cell]
275 par = ['{:.3f}'.format(x) for x in cell_to_cellpar(row.cell)]
276 dct['lengths'] = par[:3]
277 dct['angles'] = par[3:]
279 stress = row.get('stress')
280 if stress is not None:
281 dct['stress'] = ', '.join('{0:.3f}'.format(s) for s in stress)
283 dct['formula'] = Formula(row.formula).format('abc')
285 dipole = row.get('dipole')
286 if dipole is not None:
287 dct['dipole'] = ', '.join('{0:.3f}'.format(d) for d in dipole)
289 data = row.get('data')
290 if data:
291 dct['data'] = ', '.join(data.keys())
293 constraints = row.get('constraints')
294 if constraints:
295 dct['constraints'] = ', '.join(c.__class__.__name__
296 for c in constraints)
298 keys = ({'id', 'energy', 'fmax', 'smax', 'mass', 'age'} |
299 set(key_descriptions) |
300 set(row.key_value_pairs))
301 dct['table'] = []
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)
313 desc, unit = key_descriptions.get(key, ['', '', ''])[1:]
314 if unit:
315 value += ' ' + unit
316 dct['table'].append((key, desc, value))
318 return dct