Coverage for gpaw/xc/gllb/nonlocalfunctional.py: 78%

119 statements  

« prev     ^ index     » next       coverage.py v7.7.1, created at 2025-07-09 00:21 +0000

1import numpy as np 

2from ase.units import Ha 

3from gpaw.xc.functional import XCFunctional 

4 

5 

6class NonLocalFunctional(XCFunctional): 

7 def __init__(self, xcname, setup_name=None): 

8 self.contributions = [] 

9 # TODO: remove self.xcs once deprecated calculate_delta_xc() 

10 # in c_response.py is removed 

11 self.xcs = {} 

12 XCFunctional.__init__(self, xcname, 'GLLB') 

13 if setup_name is None: 

14 self.setup_name = self.name 

15 else: 

16 self.setup_name = setup_name 

17 self.mix = None 

18 self.mix_vt_sg = None 

19 self.old_vt_sg = None 

20 self.old_H_asp = {} 

21 self.response = None 

22 

23 def get_setup_name(self): 

24 return self.setup_name 

25 

26 def set_mix(self, mix): 

27 self.mix = mix 

28 

29 def initialize(self, density, hamiltonian, wfs): 

30 for contribution in self.contributions: 

31 contribution.initialize(density, hamiltonian, wfs) 

32 

33 def initialize_1d(self, ae): 

34 for contribution in self.contributions: 

35 contribution.initialize_1d(ae) 

36 

37 def calculate_impl(self, gd, n_sg, v_sg, e_g): 

38 e_g[:] = 0.0 

39 if self.mix is None: 

40 for contribution in self.contributions: 

41 contribution.calculate(e_g, n_sg, v_sg) 

42 else: 

43 cmix = self.mix 

44 if self.mix_vt_sg is None: 

45 self.mix_vt_sg = np.zeros_like(v_sg) 

46 self.old_vt_sg = np.zeros_like(v_sg) 

47 cmix = 1.0 

48 self.mix_vt_sg[:] = 0.0 

49 for contribution in self.contributions: 

50 contribution.calculate(e_g, n_sg, self.mix_vt_sg) 

51 self.mix_vt_sg = (cmix * self.mix_vt_sg 

52 + (1.0 - cmix) * self.old_vt_sg) 

53 v_sg += self.mix_vt_sg 

54 self.old_vt_sg[:] = self.mix_vt_sg 

55 

56 def calculate_paw_correction(self, setup, D_sp, dEdD_sp, a=None, 

57 addcoredensity=True): 

58 return self.calculate_energy_and_derivatives(setup, D_sp, dEdD_sp, a, 

59 addcoredensity) 

60 

61 def calculate_energy_and_derivatives(self, setup, D_sp, H_sp, a, 

62 addcoredensity=True): 

63 if setup.xc_correction is None: 

64 return 0.0 

65 Exc = 0.0 

66 # We are supposed to add to H_sp, not write directly 

67 H0_sp = H_sp 

68 H_sp = H0_sp.copy() 

69 H_sp[:] = 0.0 

70 

71 if self.mix is None: 

72 for contribution in self.contributions: 

73 Exc += contribution.calculate_energy_and_derivatives( 

74 setup, D_sp, H_sp, a, addcoredensity) 

75 else: 

76 cmix = self.mix 

77 if a not in self.old_H_asp: 

78 self.old_H_asp[a] = H_sp.copy() 

79 cmix = 1.0 

80 

81 for contribution in self.contributions: 

82 Exc += contribution.calculate_energy_and_derivatives( 

83 setup, D_sp, H_sp, a, addcoredensity) 

84 H_sp *= cmix 

85 H_sp += (1 - cmix) * self.old_H_asp[a] 

86 self.old_H_asp[a][:] = H_sp.copy() 

87 

88 H0_sp += H_sp 

89 Exc -= setup.xc_correction.e_xc0 

90 return Exc 

91 

92 def get_xc_potential_and_energy_1d(self, v_g): 

93 Exc = 0.0 

94 for contribution in self.contributions: 

95 Exc += contribution.add_xc_potential_and_energy_1d(v_g) 

96 return Exc 

97 

98 def get_smooth_xc_potential_and_energy_1d(self, vt_g): 

99 Exc = 0.0 

100 for contribution in self.contributions: 

101 Exc += contribution.add_smooth_xc_potential_and_energy_1d(vt_g) 

102 return Exc 

103 

104 def initialize_from_atomic_orbitals(self, basis_functions): 

105 for contribution in self.contributions: 

106 contribution.initialize_from_atomic_orbitals(basis_functions) 

107 

108 def get_extra_setup_data(self, extra_data): 

109 for contribution in self.contributions: 

110 contribution.get_extra_setup_data(extra_data) 

111 

112 def add_contribution(self, contribution): 

113 self.contributions.append(contribution) 

114 self.xcs[contribution.get_name()] = contribution 

115 if contribution.get_name() == 'RESPONSE': 

116 assert self.response is None 

117 self.response = contribution 

118 

119 def get_description(self): 

120 fmt = '| %-6s | %-10s | %-45s |' 

121 header = fmt % ('Weight', 'Module', 'Description') 

122 dashes = '-' * len(header) 

123 s = ['{} functional being used consists of'.format(self.name)] 

124 s += [dashes] 

125 s += [header] 

126 s += [dashes] 

127 for c in self.contributions: 

128 s += [fmt % ('%6.3f' % c.weight, c.get_name(), c.get_desc())] 

129 s += [dashes] 

130 return '\n'.join(s) 

131 

132 def summary(self, log): 

133 if self.response is not None: 

134 eref_s = self.response.eref_s 

135 source_s = self.response.eref_source_s 

136 if eref_s is not None and source_s is not None: 

137 text_s = [f'{eref * Ha:.5f} eV (from {source})' 

138 for eref, source in zip(eref_s, source_s)] 

139 log('{} response reference energy: {}\n' 

140 .format(self.name, ', '.join(text_s))) 

141 

142 def read(self, reader): 

143 for contribution in self.contributions: 

144 contribution.read(reader) 

145 

146 def write(self, writer): 

147 for contribution in self.contributions: 

148 contribution.write(writer) 

149 

150 def heeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeelp(self, olddens): 

151 # XXX This function should be removed once the deprecated 

152 # `fixdensity=True` option is removed. 

153 for contribution in self.contributions: 

154 try: 

155 contribution.heeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeelp(olddens) 

156 except AttributeError: 

157 pass