Coverage for /builds/debichem-team/python-ase/ase/calculators/test.py: 89.43%

123 statements  

« prev     ^ index     » next       coverage.py v7.5.3, created at 2025-03-06 04:00 +0000

1from math import pi 

2 

3import numpy as np 

4 

5from ase.atoms import Atoms 

6from ase.calculators.calculator import Calculator, kpts2ndarray 

7from ase.calculators.fd import calculate_numerical_forces 

8from ase.units import Bohr, Ha 

9 

10 

11def make_test_dft_calculation(): 

12 a = b = 2.0 

13 c = 6.0 

14 atoms = Atoms(positions=[(0, 0, c / 2)], 

15 symbols='H', 

16 pbc=(1, 1, 0), 

17 cell=(a, b, c), 

18 calculator=TestCalculator()) 

19 return atoms 

20 

21 

22class TestCalculator: 

23 def __init__(self, nk=8): 

24 assert nk % 2 == 0 

25 bzk = [] 

26 weights = [] 

27 ibzk = [] 

28 w = 1.0 / nk**2 

29 for i in range(-nk + 1, nk, 2): 

30 for j in range(-nk + 1, nk, 2): 

31 k = (0.5 * i / nk, 0.5 * j / nk, 0) 

32 bzk.append(k) 

33 if i >= j > 0: 

34 ibzk.append(k) 

35 if i == j: 

36 weights.append(4 * w) 

37 else: 

38 weights.append(8 * w) 

39 assert abs(sum(weights) - 1.0) < 1e-12 

40 self.bzk = np.array(bzk) 

41 self.ibzk = np.array(ibzk) 

42 self.weights = np.array(weights) 

43 

44 # Calculate eigenvalues and wave functions: 

45 self.init() 

46 

47 def init(self): 

48 nibzk = len(self.weights) 

49 nbands = 1 

50 

51 V = -1.0 

52 self.eps = 2 * V * (np.cos(2 * pi * self.ibzk[:, 0]) + 

53 np.cos(2 * pi * self.ibzk[:, 1])) 

54 self.eps.shape = (nibzk, nbands) 

55 

56 self.psi = np.zeros((nibzk, 20, 20, 60), complex) 

57 phi = np.empty((2, 2, 20, 20, 60)) 

58 z = np.linspace(-1.5, 1.5, 60, endpoint=False) 

59 for i in range(2): 

60 x = np.linspace(0, 1, 20, endpoint=False) - i 

61 for j in range(2): 

62 y = np.linspace(0, 1, 20, endpoint=False) - j 

63 r = (((x[:, None]**2 + 

64 y**2)[:, :, None] + 

65 z**2)**0.5).clip(0, 1) 

66 phi = 1.0 - r**2 * (3.0 - 2.0 * r) 

67 phase = np.exp(pi * 2j * np.dot(self.ibzk, (i, j, 0))) 

68 self.psi += phase[:, None, None, None] * phi 

69 

70 def get_pseudo_wave_function(self, band=0, kpt=0, spin=0): 

71 assert spin == 0 and band == 0 

72 return self.psi[kpt] 

73 

74 def get_eigenvalues(self, kpt=0, spin=0): 

75 assert spin == 0 

76 return self.eps[kpt] 

77 

78 def get_number_of_bands(self): 

79 return 1 

80 

81 def get_k_point_weights(self): 

82 return self.weights 

83 

84 def get_number_of_spins(self): 

85 return 1 

86 

87 def get_fermi_level(self): 

88 return 0.0 

89 

90 def get_pseudo_density(self): 

91 n = 0.0 

92 for w, eps, psi in zip(self.weights, self.eps[:, 0], self.psi): 

93 if eps >= 0.0: 

94 continue 

95 n += w * (psi * psi.conj()).real 

96 

97 n[1:] += n[:0:-1].copy() 

98 n[:, 1:] += n[:, :0:-1].copy() 

99 n += n.transpose((1, 0, 2)).copy() 

100 n /= 8 

101 return n 

102 

103 

104class TestPotential(Calculator): 

105 implemented_properties = ['energy', 'forces'] 

106 

107 def calculate(self, atoms, properties, system_changes): 

108 Calculator.calculate(self, atoms, properties, system_changes) 

109 E = 0.0 

110 R = atoms.positions 

111 F = np.zeros_like(R) 

112 for a, r in enumerate(R): 

113 D = R - r 

114 d = (D**2).sum(1)**0.5 

115 x = d - 1.0 

116 E += np.vdot(x, x) 

117 d[a] = 1 

118 F -= (x / d)[:, None] * D 

119 energy = 0.25 * E 

120 self.results = {'energy': energy, 'forces': F} 

121 

122 

123class FreeElectrons(Calculator): 

124 """Free-electron band calculator. 

125 

126 Parameters: 

127 

128 nvalence: int 

129 Number of electrons 

130 kpts: dict 

131 K-point specification. 

132 

133 Example: 

134 >>> from ase.calculators.test import FreeElectrons 

135 >>> calc = FreeElectrons(nvalence=1, kpts={'path': 'GXL'}) 

136 """ 

137 

138 implemented_properties = ['energy'] 

139 default_parameters = {'kpts': np.zeros((1, 3)), 

140 'nvalence': 0.0, 

141 'nbands': 20, 

142 'gridsize': 7} 

143 

144 def calculate(self, atoms, properties, system_changes): 

145 Calculator.calculate(self, atoms) 

146 self.kpts = kpts2ndarray(self.parameters.kpts, atoms) 

147 icell = atoms.cell.reciprocal() * 2 * np.pi * Bohr 

148 n = self.parameters.gridsize 

149 offsets = np.indices((n, n, n)).T.reshape((n**3, 1, 3)) - n // 2 

150 eps = 0.5 * (np.dot(self.kpts + offsets, icell)**2).sum(2).T 

151 eps.sort() 

152 self.eigenvalues = eps[:, :self.parameters.nbands] * Ha 

153 self.results = {'energy': 0.0} 

154 

155 def get_eigenvalues(self, kpt, spin=0): 

156 assert spin == 0 

157 return self.eigenvalues[kpt].copy() 

158 

159 def get_fermi_level(self): 

160 v = self.atoms.get_volume() / Bohr**3 

161 kF = (self.parameters.nvalence / v * 3 * np.pi**2)**(1 / 3) 

162 return 0.5 * kF**2 * Ha 

163 

164 def get_ibz_k_points(self): 

165 return self.kpts.copy() 

166 

167 def get_number_of_spins(self): 

168 return 1 

169 

170 

171def gradient_test(atoms, indices=None): 

172 """ 

173 Use numeric_force to compare analytical and numerical forces on atoms 

174 

175 If indices is None, test is done on all atoms. 

176 """ 

177 if indices is None: 

178 indices = range(len(atoms)) 

179 f = atoms.get_forces()[indices] 

180 print('{:>16} {:>20}'.format('eps', 'max(abs(df))')) 

181 for eps in np.logspace(-1, -8, 8): 

182 fn = calculate_numerical_forces(atoms, eps, indices) 

183 print(f'{eps:16.12f} {abs(fn - f).max():20.12f}') 

184 return f, fn