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
« 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
5import larch
6from larch.larchlib import get_dll
7from xraydb import atomic_mass
9F8LIB = None
11FEFF_maxpts = 150 # nex
12FEFF_maxpot = 11 # nphx
13FEFF_maxleg = 9 # legtot
14BOHR = 0.52917721067
15RYDBERG = 13.605698
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
40class Feff8L_XAFSPath(object):
41 """Feff8 Scattering Path calculation
43 A calculation requires a Potentials and Phase Shift calculation
44 in PAD format from Feff8L, and a list of scattering paths
46 Usage:
47 ------
48 # create path
49 path = Feff8L_XAFSPath(phase_file='phase.pad')
51 # list 'ipot' and labels for absorber, scatterers
52 path.list_scatterers()
54 # set coords for absorbing atom
55 path.set_absorber(x=0., y=0., z=0.)
57 # add scattering atom
58 path.add_scatterer(x=1.5, y=1.5, z=1.5, ipot=1)
60 # calculate basic (unaltered) XAFS contributions
61 path.calcuate_xafs()
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)
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
100 dargs = dict(dtype=np.float64, order='F')
101 largs = dict(dtype=np.int32, order='F')
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 = []
121 if self.phase_file is not None:
122 self.read_atoms()
123 self.set_absorber()
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
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)
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]
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]
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")
179 if len(self.atoms) < 1:
180 self.read_atoms()
182 class args:
183 pass
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
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)))))
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))))
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))
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))
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)
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)
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)
245 # unpck energies
246 for attr in ('evec', 'xivec'):
247 cdata = getattr(args, attr).contents[:]
248 setattr(self, attr, np.array(cdata))
250 nleg = self.nleg
251 nepts = self.nepts
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
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))
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
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
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)
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)
291 self.rmass = 1./rmass
294def feff8_xafs(phase_file):
295 return Feff8L_XAFSPath(phase_file=phase_file)
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##