Coverage for /Users/Newville/Codes/xraylarch/larch/xafs/feffdat.py: 83%

450 statements  

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

1#!/usr/bin/env python 

2""" 

3feffdat provides the following function related to 

4reading and dealing with Feff.data files in larch: 

5 

6 path1 = read_feffdat('feffNNNN.dat') 

7 

8returns a Feff Group -- a special variation of a Group -- for 

9the path represented by the feffNNNN.dat 

10 

11 group = ff2chi(paths) 

12 

13creates a group that contains the chi(k) for the sum of paths. 

14""" 

15import os 

16import numpy as np 

17from copy import deepcopy 

18from scipy.interpolate import UnivariateSpline 

19from lmfit import Parameters, Parameter 

20 

21from xraydb import atomic_mass, atomic_symbol 

22 

23from larch import Group, isNamedClass 

24from larch.utils.strutils import fix_varname, b32hash 

25from larch.fitting import group2params, dict2params, isParameter, param_value 

26from .xafsutils import ETOK, ktoe, set_xafsGroup, gfmt 

27from .sigma2_models import add_sigma2funcs 

28 

29SMALL_ENERGY = 1.e-6 

30 

31PATH_PARS = ('degen', 's02', 'e0', 'ei', 'deltar', 'sigma2', 'third', 'fourth') 

32FDAT_ARRS = ('real_phc', 'mag_feff', 'pha_feff', 'red_fact', 

33 'lam', 'rep', 'pha', 'amp', 'k') 

34 

35# values that will be available in calculations of Path Parameter values 

36FEFFDAT_VALUES = ('reff', 'nleg', 'degen', 'rmass', 'rnorman', 

37 'gam_ch', 'rs_int', 'vint', 'vmu', 'vfermi') 

38 

39class FeffDatFile(Group): 

40 def __init__(self, filename=None, **kws): 

41 kwargs = dict(name='feff.dat: %s' % filename) 

42 kwargs.update(kws) 

43 Group.__init__(self, **kwargs) 

44 if filename not in ('', None) and os.path.exists(filename): 

45 self._read(filename) 

46 

47 def __repr__(self): 

48 if self.filename is not None: 

49 return '<Feff.dat File Group: %s>' % self.filename 

50 return '<Feff.dat File Group (empty)>' 

51 

52 def __copy__(self): 

53 return FeffDatFile(filename=self.filename) 

54 

55 def __deepcopy__(self, memo): 

56 return FeffDatFile(filename=self.filename) 

57 

58 @property 

59 def reff(self): return self.__reff__ 

60 

61 @reff.setter 

62 def reff(self, val): pass 

63 

64 @property 

65 def nleg(self): return self.__nleg__ 

66 

67 @nleg.setter 

68 def nleg(self, val): pass 

69 

70 @property 

71 def rmass(self): 

72 """reduced mass for a path""" 

73 if self.__rmass is None: 

74 rmass = 0 

75 for atsym, iz, ipot, amass, x, y, z in self.geom: 

76 rmass += 1.0/max(1., amass) 

77 self.__rmass = 1./rmass 

78 return self.__rmass 

79 

80 @rmass.setter 

81 def rmass(self, val): pass 

82 

83 def _set_from_dict(self, **kws): 

84 self.__rmass = None 

85 for key, val in kws.items(): 

86 if key == 'rmass': 

87 continue 

88 elif key == 'reff': 

89 key = '__reff__' 

90 elif key == 'nleg': 

91 key = '__nleg__' 

92 elif key in FDAT_ARRS: 

93 val = np.array(val) 

94 setattr(self, key, val) 

95 

96 def __setstate__(self, state): 

97 (self.filename, self.title, self.version, self.shell, 

98 self.absorber, self.degen, self.__reff__, self.__nleg__, 

99 self.rnorman, self.edge, self.gam_ch, self.exch, self.vmu, self.vfermi, 

100 self.vint, self.rs_int, self.potentials, self.geom, self.__rmass, 

101 self.k, self.real_phc, self.mag_feff, self.pha_feff, 

102 self.red_fact, self.lam, self.rep, self.pha, self.amp) = state 

103 

104 self.k = np.array(self.k) 

105 self.real_phc = np.array(self.real_phc) 

106 self.mag_feff = np.array(self.mag_feff) 

107 self.pha_feff = np.array(self.pha_feff) 

108 self.red_fact = np.array(self.red_fact) 

109 self.lam = np.array(self.lam) 

110 self.rep = np.array(self.rep) 

111 self.pha = np.array(self.pha) 

112 self.amp = np.array(self.amp) 

113 

114 def __getstate__(self): 

115 return (self.filename, self.title, self.version, self.shell, 

116 self.absorber, self.degen, self.__reff__, self.__nleg__, 

117 self.rnorman, self.edge, self.gam_ch, self.exch, self.vmu, 

118 self.vfermi, self.vint, self.rs_int, self.potentials, 

119 self.geom, self.__rmass, self.k.tolist(), 

120 self.real_phc.tolist(), self.mag_feff.tolist(), 

121 self.pha_feff.tolist(), self.red_fact.tolist(), 

122 self.lam.tolist(), self.rep.tolist(), self.pha.tolist(), 

123 self.amp.tolist()) 

124 

125 

126 def _read(self, filename): 

127 try: 

128 with open(filename, 'r') as fh: 

129 lines = fh.readlines() 

130 except: 

131 print(f"Error reading Feff Data file '{filename}'") 

132 return 

133 self.filename = filename 

134 mode = 'header' 

135 self.potentials, self.geom = [], [] 

136 data = [] 

137 pcounter = 0 

138 iline = 0 

139 for line in lines: 

140 iline += 1 

141 line = line[:-1].strip() 

142 if line.startswith('#'): line = line[1:] 

143 line = line.strip() 

144 if iline == 1: 

145 self.title = line[:64].strip() 

146 self.version = line[64:].strip() 

147 continue 

148 if line.startswith('k') and line.endswith('real[p]@#'): 

149 mode = 'arrays' 

150 continue 

151 elif '----' in line[2:10]: 

152 mode = 'path' 

153 continue 

154 # 

155 if (mode == 'header' and 

156 line.startswith('Abs') or line.startswith('Pot')): 

157 words = line.replace('=', ' ').split() 

158 ipot, z, rmt, rnm = (0, 0, 0, 0) 

159 words.pop(0) 

160 if line.startswith('Pot'): 

161 ipot = int(words.pop(0)) 

162 iz = int(words[1]) 

163 rmt = float(words[3]) 

164 rnm = float(words[5]) 

165 if line.startswith('Abs'): 

166 self.shell = words[6] 

167 self.potentials.append((ipot, iz, rmt, rnm)) 

168 elif mode == 'header' and line.startswith('Gam_ch'): 

169 words = line.replace('=', ' ').split(None, 2) 

170 self.gam_ch = float(words[1]) 

171 self.exch = words[2] 

172 elif mode == 'header' and line.startswith('Mu'): 

173 words = line.replace('=', ' ').replace('eV', ' ').split() 

174 self.vmu = float(words[1]) 

175 self.vfermi = ktoe(float(words[3])) 

176 self.vint = float(words[5]) 

177 self.rs_int= float(words[7]) 

178 elif mode == 'path': 

179 pcounter += 1 

180 if pcounter == 1: 

181 w = [float(x) for x in line.split()[:5]] 

182 self.__nleg__ = int(w.pop(0)) 

183 self.degen, self.__reff__, self.rnorman, self.edge = w 

184 elif pcounter > 2: 

185 words = line.split() 

186 xyz = ["%7.4f" % float(x) for x in words[:3]] 

187 ipot = int(words[3]) 

188 iz = int(words[4]) 

189 if len(words) > 5: 

190 lab = words[5] 

191 else: 

192 lab = atomic_symbol(iz) 

193 amass = atomic_mass(iz) 

194 geom = [lab, iz, ipot, amass] + xyz 

195 if len(self.geom) == 0: 

196 self.absorber = lab 

197 self.geom.append(tuple(geom)) 

198 elif mode == 'arrays': 

199 d = np.array([float(x) for x in line.split()]) 

200 if len(d) == 7: 

201 data.append(d) 

202 data = np.array(data).transpose() 

203 self.k = data[0] 

204 self.real_phc = data[1] 

205 self.mag_feff = data[2] 

206 self.pha_feff = data[3] 

207 self.red_fact = data[4] 

208 self.lam = data[5] 

209 self.rep = data[6] 

210 self.pha = data[1] + data[3] 

211 self.amp = data[2] * data[4] 

212 self.__rmass = None # reduced mass of path 

213 

214 

215class FeffPathGroup(Group): 

216 def __init__(self, filename=None, label='', feffrun='', s02=None, degen=None, 

217 e0=None, ei=None, deltar=None, sigma2=None, third=None, 

218 fourth=None, use=True, _feffdat=None, **kws): 

219 kwargs = dict(filename=filename) 

220 kwargs.update(kws) 

221 Group.__init__(self, **kwargs) 

222 

223 self.filename = filename 

224 self.feffrun = feffrun 

225 self.label = label 

226 self.use = use 

227 self.params = None 

228 self.spline_coefs = None 

229 self.geom = [] 

230 self.shell = 'K' 

231 self.absorber = None 

232 self._feffdat = _feffdat 

233 

234 self.hashkey = 'p000' 

235 self.k = None 

236 self.chi = None 

237 

238 self.__def_degen = 1 

239 

240 if filename not in ('', None) and os.path.exists(filename): 

241 self._feffdat = FeffDatFile(filename=filename) 

242 

243 if self._feffdat is not None: 

244 self.create_spline_coefs() 

245 self.geom = self._feffdat.geom 

246 self.shell = self._feffdat.shell 

247 self.absorber = self._feffdat.absorber 

248 self.__def_degen = self._feffdat.degen 

249 

250 self.hashkey = self.__geom2label() 

251 if self.label in ('', None): 

252 self.label = self.hashkey 

253 

254 if feffrun in ('', None): 

255 try: 

256 dirname, fpfile = os.path.split(filename) 

257 parent, folder = os.path.split(dirname) 

258 self.feffrun = folder 

259 except: 

260 pass 

261 

262 self.init_path_params(degen=degen, s02=s02, e0=e0, ei=ei, 

263 deltar=deltar, sigma2=sigma2, third=third, 

264 fourth=fourth) 

265 

266 

267 def init_path_params(self, degen=None, s02=None, e0=None, ei=None, 

268 deltar=None, sigma2=None, third=None, fourth=None): 

269 """set inital values/expressions for path parameters for Feff Path""" 

270 self.degen = self.__def_degen if degen is None else degen 

271 self.s02 = 1.0 if s02 is None else s02 

272 self.e0 = 0.0 if e0 is None else e0 

273 self.ei = 0.0 if ei is None else ei 

274 self.deltar = 0.0 if deltar is None else deltar 

275 self.sigma2 = 0.0 if sigma2 is None else sigma2 

276 self.third = 0.0 if third is None else third 

277 self.fourth = 0.0 if fourth is None else fourth 

278 

279 def __repr__(self): 

280 if self.filename is not None: 

281 return 'feffpath((no_file)' 

282 return f'feffpath({self.filename})' 

283 

284 def __getstate__(self): 

285 _feffdat_state = self._feffdat.__getstate__() 

286 return (self.filename, self.label, self.feffrun, self.degen, 

287 self.s02, self.e0, self.ei, self.deltar, self.sigma2, 

288 self.third, self.fourth, self.use, _feffdat_state) 

289 

290 

291 def __setstate__(self, state): 

292 self.params = self.spline_coefs = self.k = self.chi = None 

293 self.use = True 

294 if len(state) == 12: # "use" was added after paths states were being saved 

295 (self.filename, self.label, self.feffrun, self.degen, 

296 self.s02, self.e0, self.ei, self.deltar, self.sigma2, 

297 self.third, self.fourth, _feffdat_state) = state 

298 elif len(state) == 13: 

299 (self.filename, self.label, self.feffrun, self.degen, 

300 self.s02, self.e0, self.ei, self.deltar, self.sigma2, 

301 self.third, self.fourth, self.use, _feffdat_state) = state 

302 

303 self._feffdat = FeffDatFile() 

304 self._feffdat.__setstate__(_feffdat_state) 

305 

306 self.create_spline_coefs() 

307 

308 self.geom = self._feffdat.geom 

309 self.shell = self._feffdat.shell 

310 self.absorber = self._feffdat.absorber 

311 def_degen = self._feffdat.degen 

312 

313 self.hashkey = self.__geom2label() 

314 if self.label in ('', None): 

315 self.label = self.hashkey 

316 

317 

318 def __geom2label(self): 

319 """generate label by hashing path geometry""" 

320 rep = [self._feffdat.degen, self._feffdat.shell, self.feffrun] 

321 for atom in self.geom: 

322 rep.extend(atom) 

323 rep.append("%7.4f" % self._feffdat.reff) 

324 s = "|".join([str(i) for i in rep]) 

325 return "p%s" % (b32hash(s)[:9].lower()) 

326 

327 def pathpar_name(self, parname): 

328 """ 

329 get internal name of lmfit Parameter for a path paramter, using Path's hashkey 

330 """ 

331 return f'{parname}_{self.hashkey}' 

332 

333 def __copy__(self): 

334 newpath = FeffPathGroup() 

335 newpath.__setstate__(self.__getstate__()) 

336 return newpath 

337 

338 def __deepcopy__(self, memo): 

339 newpath = FeffPathGroup() 

340 newpath.__setstate__(self.__getstate__()) 

341 return newpath 

342 

343 

344 @property 

345 def reff(self): return self._feffdat.reff 

346 

347 @reff.setter 

348 def reff(self, val): pass 

349 

350 @property 

351 def nleg(self): return self._feffdat.nleg 

352 

353 @nleg.setter 

354 def nleg(self, val): pass 

355 

356 @property 

357 def rmass(self): return self._feffdat.rmass 

358 

359 @rmass.setter 

360 def rmass(self, val): pass 

361 

362 def __repr__(self): 

363 return f'<FeffPath Group label={self.label:s}, filename={self.filename:s}, use={self.use}>' 

364 

365 def create_path_params(self, params=None): 

366 """ 

367 create Path Parameters within the current lmfit.Parameters namespace 

368 """ 

369 if params is not None: 

370 self.params = params 

371 if self.params is None: 

372 self.params = Parameters() 

373 

374 if (not isinstance(self.params, Parameters) and 

375 isinstance(self.params, dict)): 

376 self.params = dict2params(self.params) 

377 

378 if self.params._asteval.symtable.get('sigma2_debye', None) is None: 

379 add_sigma2funcs(self.params) 

380 if self.label is None: 

381 self.label = self.__geom2label() 

382 self.store_feffdat() 

383 for pname in PATH_PARS: 

384 val = getattr(self, pname) 

385 attr = 'value' 

386 if isinstance(val, str): 

387 attr = 'expr' 

388 kws = {'vary': False, attr: val} 

389 parname = self.pathpar_name(pname) 

390 self.params.add(parname, **kws) 

391 self.params[parname].is_pathparam = True 

392 

393 def create_spline_coefs(self): 

394 """pre-calculate spline coefficients for feff data""" 

395 self.spline_coefs = {} 

396 fdat = self._feffdat 

397 self.spline_coefs['pha'] = UnivariateSpline(fdat.k, fdat.pha, s=0) 

398 self.spline_coefs['amp'] = UnivariateSpline(fdat.k, fdat.amp, s=0) 

399 self.spline_coefs['rep'] = UnivariateSpline(fdat.k, fdat.rep, s=0) 

400 self.spline_coefs['lam'] = UnivariateSpline(fdat.k, fdat.lam, s=0) 

401 

402 def store_feffdat(self): 

403 """stores data about this Feff path in the Parameters 

404 symbol table for use as `reff` and in sigma2 calcs 

405 """ 

406 if (not isinstance(self.params, Parameters) and 

407 isinstance(self.params, dict)): 

408 self.params = dict2params(self.params) 

409 

410 symtab = self.params._asteval.symtable 

411 symtab['feffpath'] = self._feffdat 

412 for attr in FEFFDAT_VALUES: 

413 symtab[attr] = getattr(self._feffdat, attr) 

414 

415 

416 def __path_params(self, **kws): 

417 """evaluate path parameter value. Returns 

418 (degen, s02, e0, ei, deltar, sigma2, third, fourth) 

419 """ 

420 # put 'reff' and '_feffdat' into the symboltable so that 

421 # they can be used in constraint expressions 

422 self.store_feffdat() 

423 if self.params is None: 

424 self.create_path_params() 

425 out = [] 

426 for pname in PATH_PARS: 

427 val = kws.get(pname, None) 

428 if val is None: 

429 parname = self.pathpar_name(pname) 

430 val = self.params[parname]._getval() 

431 out.append(val) 

432 return out 

433 

434 def path_paramvals(self, **kws): 

435 (deg, s02, e0, ei, delr, ss2, c3, c4) = self.__path_params() 

436 return dict(degen=deg, s02=s02, e0=e0, ei=ei, deltar=delr, 

437 sigma2=ss2, third=c3, fourth=c4) 

438 

439 def report(self): 

440 "return text report of parameters" 

441 tmpvals = self.__path_params() 

442 pathpars = {} 

443 for pname in ('degen', 's02', 'e0', 'deltar', 

444 'sigma2', 'third', 'fourth', 'ei'): 

445 parname = self.pathpar_name(pname) 

446 if parname in self.params: 

447 pathpars[pname] = (self.params[parname].value, 

448 self.params[parname].stderr) 

449 

450 out = [f" = Path '{self.label}' = {self.absorber} {self.shell} Edge", 

451 f" feffdat file = {self.filename}, from feff run '{self.feffrun}'"] 

452 geomlabel = ' geometry atom x y z ipot' 

453 geomformat = ' %4s %s, %s, %s %d' 

454 out.append(geomlabel) 

455 

456 for atsym, iz, ipot, amass, x, y, z in self.geom: 

457 s = geomformat % (atsym, x, y, z, ipot) 

458 if ipot == 0: s = "%s (absorber)" % s 

459 out.append(s) 

460 

461 stderrs = {} 

462 out.append(' {:7s}= {:s}'.format('reff', 

463 gfmt(self._feffdat.reff))) 

464 

465 for pname in ('degen', 's02', 'e0', 'r', 

466 'deltar', 'sigma2', 'third', 'fourth', 'ei'): 

467 val = strval = getattr(self, pname, 0) 

468 parname = self.pathpar_name(pname) 

469 std = None 

470 if pname == 'r': 

471 parname = self.pathpar_name('deltar') 

472 par = self.params.get(parname, None) 

473 val = par.value + self._feffdat.reff 

474 strval = 'reff + ' + getattr(self, 'deltar', 0) 

475 std = par.stderr 

476 else: 

477 if pname in pathpars: 

478 val, std = pathpars[pname] 

479 else: 

480 par = self.params.get(parname, None) 

481 if par is not None: 

482 val = par.value 

483 std = par.stderr 

484 

485 if std is None or std <= 0: 

486 svalue = gfmt(val) 

487 else: 

488 svalue = "{:s} +/-{:s}".format(gfmt(val), gfmt(std)) 

489 if pname == 's02': 

490 pname = 'n*s02' 

491 

492 svalue = " {:7s}= {:s}".format(pname, svalue) 

493 if isinstance(strval, str): 

494 svalue = "{:s} := '{:s}'".format(svalue, strval) 

495 

496 if val == 0 and pname in ('third', 'fourth', 'ei'): 

497 continue 

498 out.append(svalue) 

499 return '\n'.join(out) 

500 

501 def calc_chi_from_params(self, params, **kws): 

502 "calculate chi(k) from Parameters, ParameterGroup, and/or kws for path parameters" 

503 if isinstance(params, Parameters): 

504 self.create_path_params(params=params) 

505 else: 

506 self.create_path_params(params=group2params(params)) 

507 self._calc_chi(**kws) 

508 

509 def _calc_chi(self, k=None, kmax=None, kstep=None, degen=None, s02=None, 

510 e0=None, ei=None, deltar=None, sigma2=None, 

511 third=None, fourth=None, debug=False, interp='cubic', **kws): 

512 """calculate chi(k) with the provided parameters""" 

513 fdat = self._feffdat 

514 if fdat.reff < 0.05: 

515 print('reff is too small to calculate chi(k)') 

516 return 

517 # make sure we have a k array 

518 if k is None: 

519 if kmax is None: 

520 kmax = 30.0 

521 kmax = min(max(fdat.k), kmax) 

522 if kstep is None: kstep = 0.05 

523 k = kstep * np.arange(int(1.01 + kmax/kstep), dtype='float64') 

524 if not self.use: 

525 self.k = k 

526 self.p = k 

527 self.chi = 0.0 * k 

528 self.chi_imag = 0.0 * k 

529 return 

530 reff = fdat.reff 

531 # get values for all the path parameters 

532 (degen, s02, e0, ei, deltar, sigma2, third, fourth) = \ 

533 self.__path_params(degen=degen, s02=s02, e0=e0, ei=ei, 

534 deltar=deltar, sigma2=sigma2, 

535 third=third, fourth=fourth) 

536 

537 # create e0-shifted energy and k, careful to look for |e0| ~= 0. 

538 en = k*k - e0*ETOK 

539 if min(abs(en)) < SMALL_ENERGY: 

540 try: 

541 en[np.where(abs(en) < 1.5*SMALL_ENERGY)] = SMALL_ENERGY 

542 except ValueError: 

543 pass 

544 # q is the e0-shifted wavenumber 

545 q = np.sign(en)*np.sqrt(abs(en)) 

546 

547 # lookup Feff.dat values (pha, amp, rep, lam) 

548 if interp.startswith('lin'): 

549 pha = np.interp(q, fdat.k, fdat.pha) 

550 amp = np.interp(q, fdat.k, fdat.amp) 

551 rep = np.interp(q, fdat.k, fdat.rep) 

552 lam = np.interp(q, fdat.k, fdat.lam) 

553 else: 

554 pha = self.spline_coefs['pha'](q) 

555 amp = self.spline_coefs['amp'](q) 

556 rep = self.spline_coefs['rep'](q) 

557 lam = self.spline_coefs['lam'](q) 

558 

559 if debug: 

560 self.debug_k = q 

561 self.debug_pha = pha 

562 self.debug_amp = amp 

563 self.debug_rep = rep 

564 self.debug_lam = lam 

565 

566 # p = complex wavenumber, and its square: 

567 pp = (rep + 1j/lam)**2 + 1j * ei * ETOK 

568 p = np.sqrt(pp) 

569 

570 # the xafs equation: 

571 cchi = np.exp(-2*reff*p.imag - 2*pp*(sigma2 - pp*fourth/3) + 

572 1j*(2*q*reff + pha + 

573 2*p*(deltar - 2*sigma2/reff - 2*pp*third/3) )) 

574 

575 cchi = degen * s02 * amp * cchi / (q*(reff + deltar)**2) 

576 cchi[0] = 2*cchi[1] - cchi[2] 

577 # outputs: 

578 self.k = k 

579 self.p = p 

580 self.chi = cchi.imag 

581 self.chi_imag = -cchi.real 

582 

583 

584 

585def path2chi(path, paramgroup=None, **kws): 

586 """calculate chi(k) for a Feff Path, 

587 optionally setting path parameter values 

588 output chi array will be written to path group 

589 

590 Parameters: 

591 ------------ 

592 path: a FeffPath Group 

593 params: lmfit Parameters or larch ParameterGroup 

594 kmax: maximum k value for chi calculation [20]. 

595 kstep: step in k value for chi calculation [0.05]. 

596 k: explicit array of k values to calculate chi. 

597 

598 Returns: 

599 --------- 

600 None - outputs are written to path group 

601 

602 """ 

603 if not isNamedClass(path, FeffPathGroup): 

604 msg('%s is not a valid Feff Path' % path) 

605 return 

606 path.calc_chi_from_params(paramgroup, **kws) 

607 

608 

609def ff2chi(paths, group=None, paramgroup=None, k=None, kmax=None, 

610 kstep=0.05, **kws): 

611 """sum chi(k) for a list of FeffPath Groups. 

612 

613 Parameters: 

614 ------------ 

615 paths: a list of FeffPath Groups or dict of {label: FeffPathGroups} 

616 paramgroup: a Parameter Group for calculating Path Parameters [None] 

617 kmax: maximum k value for chi calculation [20]. 

618 kstep: step in k value for chi calculation [0.05]. 

619 k: explicit array of k values to calculate chi. 

620 Returns: 

621 --------- 

622 group contain arrays for k and chi 

623 

624 This essentially calls path2chi() for each of the paths in the 

625 `paths` and writes the resulting arrays to group.k and group.chi. 

626 

627 """ 

628 if isinstance(paramgroup, Parameters): 

629 params = paramgroup 

630 else: 

631 params = group2params(paramgroup) 

632 

633 

634 if isinstance(paths, (list, tuple)): 

635 pathlist = paths 

636 elif isinstance(paths, dict): 

637 pathlist = list(paths.values()) 

638 else: 

639 raise ValueErrror('paths must be list, tuple, or dict') 

640 

641 if len(pathlist) == 0: 

642 return Group(k=np.linspace(0, 20, 401), 

643 chi=np.zeros(401, dtype='float64')) 

644 

645 for path in pathlist: 

646 if not isNamedClass(path, FeffPathGroup): 

647 print(f"{path} is not a valid Feff Path") 

648 return 

649 path.create_path_params(params=params) 

650 path._calc_chi(k=k, kstep=kstep, kmax=kmax) 

651 k = pathlist[0].k[:]*1.0 

652 out = np.zeros_like(k) 

653 for path in pathlist: 

654 out += path.chi 

655 

656 if group is None: 

657 group = Group() 

658 group.k = k 

659 group.chi = out 

660 return group 

661 

662def feffpath(filename='', label='', feffrun='', s02=None, degen=None, 

663 e0=None,ei=None, deltar=None, sigma2=None, third=None, 

664 fourth=None, use=True, **kws): 

665 """create a Feff Path Group from a *feffNNNN.dat* file. 

666 

667 Parameters: 

668 ----------- 

669 filename: name (full path of) *feffNNNN.dat* file 

670 label: label for path [file name] 

671 degen: path degeneracy, N [taken from file] 

672 s02: S_0^2 value or parameter [1.0] 

673 e0: E_0 value or parameter [0.0] 

674 deltar: delta_R value or parameter [0.0] 

675 sigma2: sigma^2 value or parameter [0.0] 

676 third: c_3 value or parameter [0.0] 

677 fourth: c_4 value or parameter [0.0] 

678 ei: E_i value or parameter [0.0] 

679 feffrun: label for Feff run [parent folder of Feff.dat file] 

680 use : use in sum of paths [True] 

681 

682 For all the options described as **value or parameter** either a 

683 numerical value or a Parameter (as created by param()) can be given. 

684 

685 Returns: 

686 --------- 

687 a FeffPath Group. 

688 """ 

689 if filename != '' and not os.path.exists(filename): 

690 raise ValueError(f"Feff Path file '{filename:s}' not found") 

691 return FeffPathGroup(filename=filename, label=label, feffrun=feffrun, 

692 s02=s02, degen=degen, e0=e0, ei=ei, deltar=deltar, 

693 sigma2=sigma2, third=third, fourth=fourth, use=use) 

694 

695def use_feffpath(pathcache, label, degen=None, s02=None, e0=None,ei=None, 

696 deltar=None, sigma2=None, third=None, fourth=None, use=True): 

697 """use a copy of a Feff Path from a cache of feff paths - a simply dictionary 

698 keyed by the path label, and to support in-memory paths, not read from feff.dat files 

699 

700 Parameters: 

701 ----------- 

702 pathcache: dictionary of feff paths 

703 label: label for path -- the dictionary key 

704 degen: path degeneracy, N [taken from file] 

705 s02: S_0^2 value or parameter [1.0] 

706 e0: E_0 value or parameter [0.0] 

707 deltar: delta_R value or parameter [0.0] 

708 sigma2: sigma^2 value or parameter [0.0] 

709 third: c_3 value or parameter [0.0] 

710 fourth: c_4 value or parameter [0.0] 

711 ei: E_i value or parameter [0.0] 

712 use: whether to use path in sum [True] 

713 """ 

714 path = deepcopy(pathcache[label]) 

715 path.use = use 

716 path.init_path_params(s02=s02, degen=degen, e0=e0, ei=ei, 

717 deltar=deltar, sigma2=sigma2, third=third, 

718 fourth=fourth) 

719 return path