Coverage for larch/xafs/feffdat.py: 82%

457 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2024-10-16 21:04 +0000

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""" 

15from pathlib import Path 

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 Path(filename).exists(): 

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 self.dataset = 'd001' 

234 self.hashkey = 'p001' 

235 self.k = None 

236 self.chi = None 

237 

238 self.__def_degen = 1 

239 

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

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 self.feffrun = Path(filename).parent.name 

257 except: 

258 pass 

259 

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

261 deltar=deltar, sigma2=sigma2, third=third, 

262 fourth=fourth) 

263 

264 

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

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

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

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

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

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

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

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

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

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

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

276 

277 def __repr__(self): 

278 if self.filename is not None: 

279 return 'feffpath((no_file)' 

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

281 

282 def __getstate__(self): 

283 _feffdat_state = self._feffdat.__getstate__() 

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

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

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

287 

288 

289 def __setstate__(self, state): 

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

291 self.use = True 

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

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

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

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

296 elif len(state) == 13: 

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

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

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

300 

301 self._feffdat = FeffDatFile() 

302 self._feffdat.__setstate__(_feffdat_state) 

303 

304 self.create_spline_coefs() 

305 

306 self.geom = self._feffdat.geom 

307 self.shell = self._feffdat.shell 

308 self.absorber = self._feffdat.absorber 

309 def_degen = self._feffdat.degen 

310 

311 self.hashkey = self.__geom2label() 

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

313 self.label = self.hashkey 

314 

315 

316 def __geom2label(self): 

317 """generate label by hashing path geometry""" 

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

319 for atom in self.geom: 

320 rep.extend(atom) 

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

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

323 return "p%s" % (b32hash(s)[:8].lower()) 

324 

325 def pathpar_name(self, parname): 

326 """ 

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

328 """ 

329 return f'{parname}_{self.dataset}_{self.hashkey}' 

330 

331 def __copy__(self): 

332 newpath = FeffPathGroup() 

333 newpath.__setstate__(self.__getstate__()) 

334 return newpath 

335 

336 def __deepcopy__(self, memo): 

337 newpath = FeffPathGroup() 

338 newpath.__setstate__(self.__getstate__()) 

339 return newpath 

340 

341 

342 @property 

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

344 

345 @reff.setter 

346 def reff(self, val): pass 

347 

348 @property 

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

350 

351 @nleg.setter 

352 def nleg(self, val): pass 

353 

354 @property 

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

356 

357 @rmass.setter 

358 def rmass(self, val): pass 

359 

360 def __repr__(self): 

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

362 

363 def create_path_params(self, params=None, dataset=None): 

364 """ 

365 create Path Parameters within the current lmfit.Parameters namespace 

366 """ 

367 if isinstance(params, Group): 

368 params = group2params(params) 

369 if params is not None: 

370 self.params = params 

371 if self.params is None: 

372 self.params = Parameters() 

373 if dataset is not None: 

374 self.dataset = dataset 

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

376 isinstance(self.params, dict)): 

377 self.params = dict2params(self.params) 

378 

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

380 add_sigma2funcs(self.params) 

381 if self.label is None: 

382 self.label = self.__geom2label() 

383 self.store_feffdat() 

384 for pname in PATH_PARS: 

385 val = getattr(self, pname) 

386 attr = 'value' 

387 if isinstance(val, str): 

388 attr = 'expr' 

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

390 parname = self.pathpar_name(pname) 

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

392 self.params[parname].is_pathparam = True 

393 

394 def create_spline_coefs(self): 

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

396 self.spline_coefs = {} 

397 fdat = self._feffdat 

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

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

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

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

402 

403 def store_feffdat(self): 

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

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

406 """ 

407 if self.params is None: 

408 self.create_path_params() 

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

410 isinstance(self.params, dict)): 

411 self.params = dict2params(self.params) 

412 

413 symtab = self.params._asteval.symtable 

414 symtab['feffpath'] = self._feffdat 

415 for attr in FEFFDAT_VALUES: 

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

417 

418 

419 def __path_params(self, **kws): 

420 """evaluate path parameter value. Returns 

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

422 """ 

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

424 # they can be used in constraint expressions 

425 self.store_feffdat() 

426 if self.params is None: 

427 self.create_path_params() 

428 

429 out = [] 

430 for pname in PATH_PARS: 

431 val = kws.get(pname, None) 

432 if val is None: 

433 parname = self.pathpar_name(pname) 

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

435 out.append(val) 

436 return out 

437 

438 def path_paramvals(self, **kws): 

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

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

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

442 

443 def report(self): 

444 "return text report of parameters" 

445 tmpvals = self.__path_params() 

446 pathpars = {} 

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

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

449 parname = self.pathpar_name(pname) 

450 if parname in self.params: 

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

452 self.params[parname].stderr) 

453 

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

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

456 geomlabel = ' geometry atom x y z ipot' 

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

458 out.append(geomlabel) 

459 

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

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

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

463 out.append(s) 

464 

465 stderrs = {} 

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

467 gfmt(self._feffdat.reff))) 

468 

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

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

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

472 parname = self.pathpar_name(pname) 

473 std = None 

474 if pname == 'r': 

475 parname = self.pathpar_name('deltar') 

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

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

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

479 std = par.stderr 

480 else: 

481 if pname in pathpars: 

482 val, std = pathpars[pname] 

483 else: 

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

485 if par is not None: 

486 val = par.value 

487 std = par.stderr 

488 

489 if std is None or std <= 0: 

490 svalue = gfmt(val) 

491 else: 

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

493 if pname == 's02': 

494 pname = 'n*s02' 

495 

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

497 if isinstance(strval, str): 

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

499 

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

501 continue 

502 out.append(svalue) 

503 return '\n'.join(out) 

504 

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

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

507 if isinstance(params, Parameters): 

508 self.create_path_params(params=params) 

509 else: 

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

511 self._calc_chi(**kws) 

512 

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

514 e0=None, ei=None, deltar=None, sigma2=None, 

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

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

517 fdat = self._feffdat 

518 if fdat.reff < 0.05: 

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

520 return 

521 # make sure we have a k array 

522 if k is None: 

523 if kmax is None: 

524 kmax = 30.0 

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

526 if kstep is None: kstep = 0.05 

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

528 if not self.use: 

529 self.k = k 

530 self.p = k 

531 self.chi = 0.0 * k 

532 self.chi_imag = 0.0 * k 

533 return 

534 reff = fdat.reff 

535 # get values for all the path parameters 

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

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

538 deltar=deltar, sigma2=sigma2, 

539 third=third, fourth=fourth) 

540 

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

542 en = k*k - e0*ETOK 

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

544 try: 

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

546 except ValueError: 

547 pass 

548 # q is the e0-shifted wavenumber 

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

550 

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

552 if interp.startswith('lin'): 

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

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

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

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

557 else: 

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

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

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

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

562 

563 if debug: 

564 self.debug_k = q 

565 self.debug_pha = pha 

566 self.debug_amp = amp 

567 self.debug_rep = rep 

568 self.debug_lam = lam 

569 

570 # p = complex wavenumber, and its square: 

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

572 p = np.sqrt(pp) 

573 

574 # the xafs equation: 

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

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

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

578 

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

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

581 # outputs: 

582 self.k = k 

583 self.p = p 

584 self.chi = cchi.imag 

585 self.chi_imag = -cchi.real 

586 

587 

588 

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

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

591 optionally setting path parameter values 

592 output chi array will be written to path group 

593 

594 Parameters: 

595 ------------ 

596 path: a FeffPath Group 

597 params: lmfit Parameters or larch ParameterGroup 

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

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

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

601 

602 Returns: 

603 --------- 

604 None - outputs are written to path group 

605 

606 """ 

607 if not isNamedClass(path, FeffPathGroup): 

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

609 return 

610 if params is None and paramgroup is not None: 

611 params = group2params(paramgroup) 

612 path.calc_chi_from_params(params=params, **kws) 

613 

614 

615def ff2chi(paths, group=None, params=None, k=None, kmax=None, kstep=0.05, 

616 paramgroup=None, **kws): 

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

618 

619 Parameters: 

620 ------------ 

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

622 params: lmfit.Parameters for calculating Path Parameters [None] 

623 paramgroup: (old) Group of Parameters for calculating Path Parameters [None] 

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

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

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

627 Returns: 

628 --------- 

629 group contain arrays for k and chi 

630 

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

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

633 

634 """ 

635 if params is None: 

636 if isinstance(paramgroup, Parameters): 

637 params = paramgroup 

638 else: 

639 params = group2params(paramgroup) 

640 

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

642 pathlist = paths 

643 elif isinstance(paths, dict): 

644 pathlist = list(paths.values()) 

645 else: 

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

647 

648 if len(pathlist) == 0: 

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

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

651 

652 for path in pathlist: 

653 if not isNamedClass(path, FeffPathGroup): 

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

655 return 

656 path.create_path_params(params=params) 

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

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

659 out = np.zeros_like(k) 

660 for path in pathlist: 

661 out += path.chi 

662 

663 if group is None: 

664 group = Group() 

665 group.k = k 

666 group.chi = out 

667 return group 

668 

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

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

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

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

673 

674 Parameters: 

675 ----------- 

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

677 label: label for path [file name] 

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

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

680 e0: E_0 value or parameter [0.0] 

681 deltar: delta_R value or parameter [0.0] 

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

683 third: c_3 value or parameter [0.0] 

684 fourth: c_4 value or parameter [0.0] 

685 ei: E_i value or parameter [0.0] 

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

687 use : use in sum of paths [True] 

688 

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

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

691 

692 Returns: 

693 --------- 

694 a FeffPath Group. 

695 """ 

696 if filename != '' and not Path(filename).exists(): 

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

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

699 s02=s02, degen=degen, e0=e0, ei=ei, deltar=deltar, 

700 sigma2=sigma2, third=third, fourth=fourth, use=use) 

701 

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

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

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

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

706 

707 Parameters: 

708 ----------- 

709 pathcache: dictionary of feff paths 

710 label: label for path -- the dictionary key 

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

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

713 e0: E_0 value or parameter [0.0] 

714 deltar: delta_R value or parameter [0.0] 

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

716 third: c_3 value or parameter [0.0] 

717 fourth: c_4 value or parameter [0.0] 

718 ei: E_i value or parameter [0.0] 

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

720 """ 

721 path = deepcopy(pathcache[label]) 

722 path.use = use 

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

724 deltar=deltar, sigma2=sigma2, third=third, 

725 fourth=fourth) 

726 return path