Coverage for /Users/Newville/Codes/xraylarch/larch/xafs/feff8lpath.py: 18%

190 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-11-09 10:08 -0600

1import numpy as np 

2import ctypes 

3from ctypes import POINTER, pointer, c_int, c_long, c_char, c_char_p, c_double 

4 

5import larch 

6from larch.larchlib import get_dll 

7from xraydb import atomic_mass 

8 

9F8LIB = None 

10 

11FEFF_maxpts = 150 # nex 

12FEFF_maxpot = 11 # nphx 

13FEFF_maxleg = 9 # legtot 

14BOHR = 0.52917721067 

15RYDBERG = 13.605698 

16 

17def with_phase_file(fcn): 

18 """decorator to ensure that the wrapped function either 

19 has a non-None 'phase_file' argument or that that 

20 self.phase_file is not None 

21 """ 

22 errmsg = "function '%s' needs a non-None phase_file" 

23 def wrapper(*args, **keywords): 

24 "needs phase_file" 

25 phase_file = keywords.get('phase_file', None) 

26 if phase_file is None: 

27 phase_file = getattr(args[0], 'phase_file', None) 

28 if phase_file is None: 

29 raise AttributeError(errmsg % fcn.__name__) 

30 else: 

31 setattr(args[0], 'phase_file', phase_file) 

32 # raise Warning(errmsg % fcn.__name__) 

33 return fcn(*args, **keywords) 

34 wrapper.__doc__ = fcn.__doc__ 

35 wrapper.__name__ = fcn.__name__ 

36 wrapper.__filename__ = fcn.__code__.co_filename 

37 wrapper.__dict__.update(fcn.__dict__) 

38 return wrapper 

39 

40class Feff8L_XAFSPath(object): 

41 """Feff8 Scattering Path calculation 

42 

43 A calculation requires a Potentials and Phase Shift calculation 

44 in PAD format from Feff8L, and a list of scattering paths 

45 

46 Usage: 

47 ------ 

48 # create path 

49 path = Feff8L_XAFSPath(phase_file='phase.pad') 

50 

51 # list 'ipot' and labels for absorber, scatterers 

52 path.list_scatterers() 

53 

54 # set coords for absorbing atom 

55 path.set_absorber(x=0., y=0., z=0.) 

56 

57 # add scattering atom 

58 path.add_scatterer(x=1.5, y=1.5, z=1.5, ipot=1) 

59 

60 # calculate basic (unaltered) XAFS contributions 

61 path.calcuate_xafs() 

62 

63 """ 

64 def __init__(self, phase_file=None, title=''): 

65 global F8LIB 

66 if F8LIB is None: 

67 try: 

68 F8LIB = get_dll('feff8lpath') 

69 except: 

70 pass 

71 self.reset(phase_file=phase_file, title=title) 

72 

73 def reset(self, phase_file=None, title=''): 

74 """reset all path data""" 

75 self.phase_file = None 

76 if phase_file is not None: 

77 self.phase_file = phase_file 

78 self.index = 9999 

79 self.degen = 1. 

80 self.nnnn_out = False 

81 self.json_out = False 

82 self.verbose = False 

83 self.ipol = 0 

84 self.ellip = 0. 

85 self.nepts = 0 

86 self.genfmt_order = 2 

87 self.version= "" 

88 self.exch = "" 

89 self.title = title 

90 self.filename = "%s_%s" % (self.phase_file, self.title) 

91 self.rs_int = 0. 

92 self.vint = 0. 

93 self.mu = 0. 

94 self.edge = 0. 

95 self.kf = 0. 

96 self.rnorman = 0. 

97 self.gam_ch = 0. 

98 self.nepts = FEFF_maxpts 

99 

100 dargs = dict(dtype=np.float64, order='F') 

101 largs = dict(dtype=np.int32, order='F') 

102 

103 self.evec = np.zeros(3, **dargs) 

104 self.xivec = np.zeros(3, **dargs) 

105 self.ipot = np.zeros(1+FEFF_maxleg, **largs) 

106 self.beta = np.zeros(1+FEFF_maxleg, **dargs) 

107 self.eta = np.zeros(2+FEFF_maxleg, **dargs) 

108 self.ri = np.zeros(FEFF_maxleg, **dargs) 

109 self.rat = np.zeros((3, 2+FEFF_maxleg), **dargs) 

110 self.iz = np.zeros(1+FEFF_maxpot, **largs) 

111 self.k = np.zeros(FEFF_maxpts, **dargs) 

112 self.real_phc = np.zeros(FEFF_maxpts, **dargs) 

113 self.mag_feff = np.zeros(FEFF_maxpts, **dargs) 

114 self.pha_feff = np.zeros(FEFF_maxpts, **dargs) 

115 self.red_fact = np.zeros(FEFF_maxpts, **dargs) 

116 self.lam = np.zeros(FEFF_maxpts, **dargs) 

117 self.rep = np.zeros(FEFF_maxpts, **dargs) 

118 self.nleg = 1 

119 self.atoms = [] 

120 

121 if self.phase_file is not None: 

122 self.read_atoms() 

123 self.set_absorber() 

124 

125 @with_phase_file 

126 def read_atoms(self): 

127 """read atoms ipot, iz, symbol""" 

128 self.atoms = [] 

129 with open(self.phase_file,'r') as fh: 

130 line1_words = fh.readline().strip().split() 

131 text = fh.readlines() 

132 npots = int(line1_words[4]) 

133 for line in text[4:]: 

134 if not line.startswith('$'): 

135 words = line.split() 

136 self.atoms.append((int(words[1]), words[2])) 

137 if len(self.atoms) > npots: 

138 break 

139 

140 @with_phase_file 

141 def list_atoms(self): 

142 """list Feff Potentials atoms ('ipots') fo phase file""" 

143 if len(self.atoms) < 1: 

144 self.read_atoms() 

145 out = ["# Potential Z Symbol"] 

146 for ipot, atom in enumerate(self.atoms): 

147 out.append(" %2i %3i %s" % (ipot, atom[0], atom[1])) 

148 return "\n".join(out) 

149 

150 @with_phase_file 

151 def set_absorber(self, x=0., y=0., z=0., phase_file=None): 

152 """set coordinates for absorbing atom ('ipot'=0)""" 

153 self.rat[0, 0] = x 

154 self.rat[1, 0] = y 

155 self.rat[2, 0] = z 

156 self.rat[0, self.nleg] = self.rat[0, 0] 

157 self.rat[1, self.nleg] = self.rat[1, 0] 

158 self.rat[2, self.nleg] = self.rat[2, 0] 

159 self.ipot[self.nleg] = self.ipot[0] 

160 

161 @with_phase_file 

162 def add_scatterer(self, x=0., y=0., z=0., ipot=1, phase_file=None): 

163 self.rat[0, self.nleg] = x 

164 self.rat[1, self.nleg] = y 

165 self.rat[2, self.nleg] = z 

166 self.ipot[self.nleg] = ipot 

167 self.nleg += 1 

168 # set final atom coords to same as absorber 

169 self.rat[0, self.nleg] = self.rat[0, 0] 

170 self.rat[1, self.nleg] = self.rat[1, 0] 

171 self.rat[2, self.nleg] = self.rat[2, 0] 

172 self.ipot[self.nleg] = self.ipot[0] 

173 

174 @with_phase_file 

175 def calculate_xafs(self, phase_file=None): 

176 if F8LIB is None: 

177 raise ValueError("Feff8 Dynamic library not found") 

178 

179 if len(self.atoms) < 1: 

180 self.read_atoms() 

181 

182 class args: 

183 pass 

184 

185 # strings / char*. Note fixed length to match Fortran 

186 args.phase_file = (self.phase_file + ' '*256)[:256] 

187 args.exch_label = ' '*8 

188 args.genfmt_version = ' '*30 

189 

190 # integers, including booleans 

191 for attr in ('index', 'nleg', 'genfmt_order', 'ipol', 'nnnn_out', 

192 'json_out', 'verbose', 'nepts'): 

193 setattr(args, attr, pointer(c_long(int(getattr(self, attr))))) 

194 

195 # doubles 

196 for attr in ('degen', 'rs_int', 'vint', 'mu', 'edge', 'kf', 'rnorman', 

197 'gam_ch', 'ellip'): 

198 setattr(args, attr, pointer(c_double(getattr(self, attr)))) 

199 

200 # integer arrays 

201 args.ipot = self.ipot.ctypes.data_as(POINTER(self.ipot.size*c_int)) 

202 args.iz = self.iz.ctypes.data_as(POINTER(self.iz.size*c_int)) 

203 

204 # double arrays 

205 # print(" Rat 0 ", self.rat) 

206 for attr in ('evec', 'xivec', 'ri', 'beta', 'eta', 

207 'k', 'real_phc', 'mag_feff', 'pha_feff', 

208 'red_fact', 'lam', 'rep'): 

209 arr = getattr(self, attr) 

210 cdata = arr.ctypes.data_as(POINTER(arr.size*c_double)) 

211 setattr(args, attr, cdata) 

212 # handle rat (in atomic units) 

213 rat_atomic = self.rat/BOHR 

214 args.rat = (rat_atomic).ctypes.data_as(POINTER(rat_atomic.size*c_double)) 

215 

216 onepath = F8LIB.calc_onepath 

217 # print(" Calc with onepath ", onepath, rat_atomic) 

218 # print(" args rat = ", args.rat.contents[:]) 

219 x = onepath(args.phase_file, args.index, args.nleg, args.degen, 

220 args.genfmt_order, args.exch_label, args.rs_int, args.vint, 

221 args.mu, args.edge, args.kf, args.rnorman, 

222 args.gam_ch, args.genfmt_version, args.ipot, args.rat, 

223 args.iz, args.ipol, args.evec, args.ellip, args.xivec, 

224 args.nnnn_out, args.json_out, args.verbose, args.ri, 

225 args.beta, args.eta, args.nepts, args.k, 

226 args.real_phc, args.mag_feff, args.pha_feff, 

227 args.red_fact, args.lam, args.rep) 

228 

229 self.exch = args.exch_label.strip() 

230 self.version = args.genfmt_version.strip() 

231 # print(" Calc with onepath done") 

232 # unpack integers/floats 

233 for attr in ('index', 'nleg', 'genfmt_order', 'degen', 'rs_int', 

234 'vint', 'mu', 'edge', 'kf', 'rnorman', 'gam_ch', 

235 'ipol', 'ellip', 'nnnn_out', 'json_out', 'verbose', 

236 'nepts'): 

237 setattr(self, attr, getattr(args, attr).contents.value) 

238 

239 # some data needs recasting, reformatting 

240 self.mu *= (2*RYDBERG) 

241 self.nnnn_out = bool(self.nnnn_out) 

242 self.json_out = bool(self.json_out) 

243 self.verbose = bool(self.verbose) 

244 

245 # unpck energies 

246 for attr in ('evec', 'xivec'): 

247 cdata = getattr(args, attr).contents[:] 

248 setattr(self, attr, np.array(cdata)) 

249 

250 nleg = self.nleg 

251 nepts = self.nepts 

252 

253 # arrays of length 'nepts' 

254 for attr in ('k', 'real_phc', 'mag_feff', 'pha_feff', 

255 'red_fact', 'lam', 'rep'): 

256 cdata = getattr(args, attr).contents[:nepts] 

257 setattr(self, attr, np.array(cdata)) 

258 self.pha = self.real_phc + self.pha_feff 

259 self.amp = self.red_fact * self.mag_feff 

260 

261 # unpack arrays of length 'nleg': 

262 for attr in ('ipot', 'beta', 'eta', 'ri'): 

263 cdata = getattr(args, attr).contents[:] 

264 setattr(self, attr, np.array(cdata)) 

265 

266 # rat is sort of special, and calculate reff too: 

267 rat = args.rat.contents[:] 

268 rat = np.array(rat).reshape(2+FEFF_maxleg, 3).transpose() 

269 self.rat = BOHR*rat 

270 

271 _rat = self.rat.T 

272 reff = 0. 

273 for i, atom in enumerate(_rat[1:]): 

274 prev = _rat[i,:] 

275 reff += np.sqrt( (prev[0]-atom[0])**2 + 

276 (prev[1]-atom[1])**2 + 

277 (prev[2]-atom[2])**2 ) 

278 self.reff = reff /2.0 

279 

280 self.geom = [] 

281 rmass = 0. 

282 for i in range(nleg): 

283 ipot = int(self.ipot[i]) 

284 iz, sym = self.atoms[ipot] 

285 mass = atomic_mass(iz) 

286 

287 x, y, z = _rat[i][0], _rat[i][1], _rat[i][2] 

288 self.geom.append((str(sym), iz, ipot, x, y, z)) 

289 rmass += 1.0/max(1.0, mass) 

290 

291 self.rmass = 1./rmass 

292 

293 

294def feff8_xafs(phase_file): 

295 return Feff8L_XAFSPath(phase_file=phase_file) 

296 

297 

298 

299## def initializeLarchPlugin(_larch=None): 

300# """initialize F8LIB""" 

301# if _larch is not None: 

302# global F8LIB 

303# if F8LIB is None: 

304# try: 

305# F8LIB = get_dll('feff8lpath') 

306# except: 

307# pass 

308##