Coverage for /builds/kinetik161/ase/ase/dft/bee.py: 90.91%
110 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-12-10 11:04 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-12-10 11:04 +0000
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 parprint, world
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."""
25 def __init__(self,
26 atoms: Union[Atoms, DFTCalculator] = None,
27 e: float = None,
28 contribs: np.ndarray = None,
29 xc: str = None,
30 verbose: bool = True):
31 if (atoms is not None or contribs is not None or xc is not None):
32 if atoms is None:
33 assert e is not None
34 assert contribs is not None
35 assert xc is not None
36 else:
37 if isinstance(atoms, Atoms):
38 calc = atoms.calc
39 self.atoms = atoms
40 else:
41 calc = atoms
42 self.atoms = calc.atoms
43 self.calc = calc
44 xc = self.calc.get_xc_functional()
45 self.e = e
46 self.contribs = contribs
47 self.xc = xc
48 self.verbose = verbose
49 self.done = False
50 if self.xc in ['BEEF-vdW', 'BEEF', 'PBE']:
51 self.beef_type = 'beefvdw'
52 elif self.xc == 'mBEEF':
53 self.beef_type = 'mbeef'
54 elif self.xc == 'mBEEF-vdW':
55 self.beef_type = 'mbeefvdw'
56 else:
57 raise NotImplementedError(f'No ensemble for xc = {self.xc}')
59 def get_ensemble_energies(self,
60 size: int = 2000,
61 seed: int = 0) -> np.ndarray:
62 """Returns an array of ensemble total energies"""
63 self.seed = seed
64 if self.verbose:
65 parprint(self.beef_type, 'ensemble started')
67 if self.contribs is None:
68 self.contribs = self.calc.get_nonselfconsistent_energies(
69 self.beef_type)
70 self.e = self.calc.get_potential_energy(self.atoms)
71 if self.beef_type == 'beefvdw':
72 assert len(self.contribs) == 32
73 coefs = self.get_beefvdw_ensemble_coefs(size, seed)
74 elif self.beef_type == 'mbeef':
75 assert len(self.contribs) == 64
76 coefs = self.get_mbeef_ensemble_coefs(size, seed)
77 elif self.beef_type == 'mbeefvdw':
78 assert len(self.contribs) == 28
79 coefs = self.get_mbeefvdw_ensemble_coefs(size, seed)
80 self.de = np.dot(coefs, self.contribs)
81 self.done = True
83 if self.verbose:
84 parprint(self.beef_type, 'ensemble finished')
86 return self.e + self.de
88 def get_beefvdw_ensemble_coefs(self, size=2000, seed=0):
89 """Perturbation coefficients of the BEEF-vdW ensemble"""
90 from ase.dft.pars_beefvdw import uiOmega as omega
91 assert np.shape(omega) == (31, 31)
93 W, V, generator = self.eigendecomposition(omega, seed)
94 RandV = generator.randn(31, size)
96 for j in range(size):
97 v = RandV[:, j]
98 coefs_i = (np.dot(np.dot(V, np.diag(np.sqrt(W))), v)[:])
99 if j == 0:
100 ensemble_coefs = coefs_i
101 else:
102 ensemble_coefs = np.vstack((ensemble_coefs, coefs_i))
103 PBEc_ens = -ensemble_coefs[:, 30]
104 return (np.vstack((ensemble_coefs.T, PBEc_ens))).T
106 def get_mbeef_ensemble_coefs(self, size=2000, seed=0):
107 """Perturbation coefficients of the mBEEF ensemble"""
108 from ase.dft.pars_mbeef import uiOmega as omega
109 assert np.shape(omega) == (64, 64)
111 W, V, generator = self.eigendecomposition(omega, seed)
112 mu, sigma = 0.0, 1.0
113 rand = np.array(generator.normal(mu, sigma, (len(W), size)))
114 return (np.sqrt(2) * np.dot(np.dot(V, np.diag(np.sqrt(W))),
115 rand)[:]).T
117 def get_mbeefvdw_ensemble_coefs(self, size=2000, seed=0):
118 """Perturbation coefficients of the mBEEF-vdW ensemble"""
119 from ase.dft.pars_mbeefvdw import uiOmega as omega
120 assert np.shape(omega) == (28, 28)
122 W, V, generator = self.eigendecomposition(omega, seed)
123 mu, sigma = 0.0, 1.0
124 rand = np.array(generator.normal(mu, sigma, (len(W), size)))
125 return (np.sqrt(2) * np.dot(np.dot(V, np.diag(np.sqrt(W))), rand)[:]).T
127 def eigendecomposition(self, omega, seed=0):
128 u, s, v = np.linalg.svd(omega) # unsafe: W, V = np.linalg.eig(omega)
129 generator = np.random.RandomState(seed)
130 return s, v.T, generator
132 def write(self, fname):
133 """Write ensemble data file"""
134 if not fname.endswith('.bee'):
135 fname += '.bee'
136 assert self.done
137 if world.rank == 0:
138 if os.path.isfile(fname):
139 os.rename(fname, fname + '.old')
140 obj = [self.e, self.de, self.contribs, self.seed, self.xc]
141 with open(fname, 'w') as fd:
142 write_json(fd, obj)
145def readbee(fname: str, all: bool = False):
146 if not fname.endswith('.bee'):
147 fname += '.bee'
148 with open(fname) as fd:
149 e, de, contribs, seed, xc = read_json(fd, always_array=False)
150 if all:
151 return e, de, contribs, seed, xc
152 else:
153 return e + de
156def BEEF_Ensemble(*args, **kwargs):
157 import warnings
158 warnings.warn('Please use BEEFEnsemble instead of BEEF_Ensemble.')
159 return BEEFEnsemble(*args, **kwargs)