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
1import os
2from typing import Any, Union
4import numpy as np
6from ase import Atoms
7from ase.io.jsonio import read_json, write_json
8from ase.parallel import world, parprint
10DFTCalculator = Any
13def ensemble(energy: float,
14 contributions: np.ndarray,
15 xc: str,
16 verbose: bool = False) -> np.ndarray:
17 """Returns an array of ensemble total energies."""
18 ensemble = BEEFEnsemble(None, energy, contributions, xc, verbose)
19 return ensemble.get_ensemble_energies()
22class BEEFEnsemble:
23 """BEEF type ensemble error estimation."""
24 def __init__(self,
25 atoms: Union[Atoms, DFTCalculator] = None,
26 e: float = None,
27 contribs: np.ndarray = None,
28 xc: str = None,
29 verbose: bool = True):
30 if (atoms is not None or contribs is not None or xc is not None):
31 if atoms is None:
32 assert e is not None
33 assert contribs is not None
34 assert xc is not None
35 else:
36 if isinstance(atoms, Atoms):
37 calc = atoms.calc
38 self.atoms = atoms
39 else:
40 calc = atoms
41 self.atoms = calc.atoms
42 self.calc = calc
43 xc = self.calc.get_xc_functional()
44 self.e = e
45 self.contribs = contribs
46 self.xc = xc
47 self.verbose = verbose
48 self.done = False
49 if self.xc in ['BEEF-vdW', 'BEEF', 'PBE']:
50 self.beef_type = 'beefvdw'
51 elif self.xc == 'mBEEF':
52 self.beef_type = 'mbeef'
53 elif self.xc == 'mBEEF-vdW':
54 self.beef_type = 'mbeefvdw'
55 else:
56 raise NotImplementedError(f'No ensemble for xc = {self.xc}')
58 def get_ensemble_energies(self,
59 size: int = 2000,
60 seed: int = 0) -> np.ndarray:
61 """Returns an array of ensemble total energies"""
62 self.seed = seed
63 if self.verbose:
64 parprint(self.beef_type, 'ensemble started')
66 if self.contribs is None:
67 self.contribs = self.calc.get_nonselfconsistent_energies(
68 self.beef_type)
69 self.e = self.calc.get_potential_energy(self.atoms)
70 if self.beef_type == 'beefvdw':
71 assert len(self.contribs) == 32
72 coefs = self.get_beefvdw_ensemble_coefs(size, seed)
73 elif self.beef_type == 'mbeef':
74 assert len(self.contribs) == 64
75 coefs = self.get_mbeef_ensemble_coefs(size, seed)
76 elif self.beef_type == 'mbeefvdw':
77 assert len(self.contribs) == 28
78 coefs = self.get_mbeefvdw_ensemble_coefs(size, seed)
79 self.de = np.dot(coefs, self.contribs)
80 self.done = True
82 if self.verbose:
83 parprint(self.beef_type, 'ensemble finished')
85 return self.e + self.de
87 def get_beefvdw_ensemble_coefs(self, size=2000, seed=0):
88 """Perturbation coefficients of the BEEF-vdW ensemble"""
89 from ase.dft.pars_beefvdw import uiOmega as omega
90 assert np.shape(omega) == (31, 31)
92 W, V, generator = self.eigendecomposition(omega, seed)
93 RandV = generator.randn(31, size)
95 for j in range(size):
96 v = RandV[:, j]
97 coefs_i = (np.dot(np.dot(V, np.diag(np.sqrt(W))), v)[:])
98 if j == 0:
99 ensemble_coefs = coefs_i
100 else:
101 ensemble_coefs = np.vstack((ensemble_coefs, coefs_i))
102 PBEc_ens = -ensemble_coefs[:, 30]
103 return (np.vstack((ensemble_coefs.T, PBEc_ens))).T
105 def get_mbeef_ensemble_coefs(self, size=2000, seed=0):
106 """Perturbation coefficients of the mBEEF ensemble"""
107 from ase.dft.pars_mbeef import uiOmega as omega
108 assert np.shape(omega) == (64, 64)
110 W, V, generator = self.eigendecomposition(omega, seed)
111 mu, sigma = 0.0, 1.0
112 rand = np.array(generator.normal(mu, sigma, (len(W), size)))
113 return (np.sqrt(2) * np.dot(np.dot(V, np.diag(np.sqrt(W))),
114 rand)[:]).T
116 def get_mbeefvdw_ensemble_coefs(self, size=2000, seed=0):
117 """Perturbation coefficients of the mBEEF-vdW ensemble"""
118 from ase.dft.pars_mbeefvdw import uiOmega as omega
119 assert np.shape(omega) == (28, 28)
121 W, V, generator = self.eigendecomposition(omega, seed)
122 mu, sigma = 0.0, 1.0
123 rand = np.array(generator.normal(mu, sigma, (len(W), size)))
124 return (np.sqrt(2) * np.dot(np.dot(V, np.diag(np.sqrt(W))), rand)[:]).T
126 def eigendecomposition(self, omega, seed=0):
127 u, s, v = np.linalg.svd(omega) # unsafe: W, V = np.linalg.eig(omega)
128 generator = np.random.RandomState(seed)
129 return s, v.T, generator
131 def write(self, fname):
132 """Write ensemble data file"""
133 if not fname.endswith('.bee'):
134 fname += '.bee'
135 assert self.done
136 if world.rank == 0:
137 if os.path.isfile(fname):
138 os.rename(fname, fname + '.old')
139 obj = [self.e, self.de, self.contribs, self.seed, self.xc]
140 with open(fname, 'w') as fd:
141 write_json(fd, obj)
144def readbee(fname: str, all: bool = False):
145 if not fname.endswith('.bee'):
146 fname += '.bee'
147 with open(fname, 'r') as fd:
148 e, de, contribs, seed, xc = read_json(fd, always_array=False)
149 if all:
150 return e, de, contribs, seed, xc
151 else:
152 return e + de
155def BEEF_Ensemble(*args, **kwargs):
156 import warnings
157 warnings.warn('Please use BEEFEnsemble instead of BEEF_Ensemble.')
158 return BEEFEnsemble(*args, **kwargs)