Coverage for /Users/Newville/Codes/xraylarch/larch/xafs/rebin_xafs.py: 11%

73 statements  

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

1import numpy as np 

2 

3from larch import Group, Make_CallArgs, parse_group_args 

4from larch.math import index_of, interp1d, remove_dups 

5from .xafsutils import ktoe, etok, TINY_ENERGY 

6 

7@Make_CallArgs(["energy", "mu"]) 

8def sort_xafs(energy, mu=None, group=None, fix_repeats=True, overwrite=True): 

9 """sort energy, mu pair of XAFS data so that energy is monotonically increasing 

10 

11 Arguments 

12 --------- 

13 energy input energy array 

14 mu input mu array 

15 group output group 

16 fix_repeats bool, whether to fix repeated energies 

17 overwrite bool, whether to overwrite arrays [True] 

18 

19 Returns 

20 ------- 

21 None 

22 

23 if overwrite is False, a group named 'sorted' will be created 

24 in the output group, with sorted energy and mu arrays 

25 

26 (if the output group is None, _sys.xafsGroup will be written to) 

27 

28 """ 

29 energy, mu, group = parse_group_args(energy, members=('energy', 'mu'), 

30 defaults=(mu,), group=group, 

31 fcn_name='sort_xafs') 

32 

33 indices = np.argsort(energy) 

34 new_energy = energy[indices] 

35 new_mu = mu[indices] 

36 

37 if fix_repeats: 

38 new_energy = remove_dups(new_energy, tiny=TINY_ENERGY) 

39 

40 if not overwrite: 

41 group.sorted = Group(energy=new_energy, mu=new_mu) 

42 else: 

43 group.energy = new_energy 

44 group.mu = new_mu 

45 return 

46 

47 

48@Make_CallArgs(["energy", "mu"]) 

49def rebin_xafs(energy, mu=None, group=None, e0=None, pre1=None, pre2=-30, 

50 pre_step=2, xanes_step=None, exafs1=15, exafs2=None, 

51 exafs_kstep=0.05, method='centroid'): 

52 """rebin XAFS energy and mu to a 'standard 3 region XAFS scan' 

53 

54 Arguments 

55 --------- 

56 energy input energy array 

57 mu input mu array 

58 group output group 

59 e0 energy reference -- all energy values are relative to this 

60 pre1 start of pre-edge region [1st energy point] 

61 pre2 end of pre-edge region, start of XANES region [-30] 

62 pre_step energy step for pre-edge region [2] 

63 xanes_step energy step for XANES region [see note] 

64 exafs1 end of XANES region, start of EXAFS region [15] 

65 exafs2 end of EXAFS region [last energy point] 

66 exafs_kstep k-step for EXAFS region [0.05] 

67 method one of 'boxcar', 'centroid' ['centroid'] 

68 

69 Returns 

70 ------- 

71 None 

72 

73 A group named 'rebinned' will be created in the output group, with the 

74 following attributes: 

75 energy new energy array 

76 mu mu for energy array 

77 e0 e0 copied from current group 

78 

79 (if the output group is None, _sys.xafsGroup will be written to) 

80 

81 Notes 

82 ------ 

83 1 If the first argument is a Group, it must contain 'energy' and 'mu'. 

84 See First Argrument Group in Documentation 

85 

86 2 If xanes_step is None, it will be found from the data as E0/25000, 

87 truncated down to the nearest 0.05: xanes_step = 0.05*max(1, int(e0/1250.0)) 

88 

89 

90 3 The EXAFS region will be spaced in k-space 

91 

92 4 The rebinned data is found by determining which segments of the 

93 input energy correspond to each bin in the new energy array. That 

94 is, each input energy is assigned to exactly one bin in the new 

95 array. For each new energy bin, the new value is selected from the 

96 data in the segment as either 

97 a) linear interpolation if there are fewer than 3 points in the segment. 

98 b) mean value ('boxcar') 

99 c) centroid ('centroid') 

100 

101 """ 

102 energy, mu, group = parse_group_args(energy, members=('energy', 'mu'), 

103 defaults=(mu,), group=group, 

104 fcn_name='rebin_xafs') 

105 

106 if e0 is None: 

107 e0 = getattr(group, 'e0', None) 

108 

109 if e0 is None: 

110 raise ValueError("need e0") 

111 

112 if pre1 is None: 

113 pre1 = pre_step*int((min(energy) - e0)/pre_step) 

114 

115 if exafs2 is None: 

116 exafs2 = max(energy) - e0 

117 

118 # determine xanes step size: 

119 # find mean of energy difference within 10 eV of E0 

120 nx1 = index_of(energy, e0-10) 

121 nx2 = index_of(energy, e0+10) 

122 de_mean = np.diff(energy[nx1:nx1]).mean() 

123 if xanes_step is None: 

124 xanes_step = 0.05 * max(1, int(e0 / 1250.0)) # E0/25000, round down to 0.05 

125 

126 # create new energy array from the 3 segments (pre, xanes, exafs) 

127 en = [] 

128 for start, stop, step, isk in ((pre1, pre2, pre_step, False), 

129 (pre2, exafs1, xanes_step, False), 

130 (exafs1, exafs2, exafs_kstep, True)): 

131 if isk: 

132 start = etok(start) 

133 stop = etok(stop) 

134 

135 npts = 1 + int(0.1 + abs(stop - start) / step) 

136 reg = np.linspace(start, stop, npts) 

137 if isk: 

138 reg = ktoe(reg) 

139 en.extend(e0 + reg[:-1]) 

140 

141 # find the segment boundaries of the old energy array 

142 bounds = [index_of(energy, e) for e in en] 

143 mu_out = [] 

144 err_out = [] 

145 

146 j0 = 0 

147 for i in range(len(en)): 

148 if i == len(en) - 1: 

149 j1 = len(energy) - 1 

150 else: 

151 j1 = int((bounds[i] + bounds[i+1] + 1)/2.0) 

152 if i == 0 and j0 == 0: 

153 j0 = index_of(energy, en[0]-5) 

154 # if not enough points in segment, do interpolation 

155 if (j1 - j0) < 3: 

156 jx = j1 + 1 

157 if (jx - j0) < 3: 

158 jx += 1 

159 

160 val = interp1d(energy[j0:jx], mu[j0:jx], en[i]) 

161 err = mu[j0:jx].std() 

162 if np.isnan(val): 

163 j0 = max(0, j0-1) 

164 jx = min(len(energy), jx+1) 

165 val = interp1d(energy[j0:jx], mu[j0:jx], en[i]) 

166 err = mu[j0:jx].std() 

167 else: 

168 if method.startswith('box'): 

169 val = mu[j0:j1].mean() 

170 else: 

171 val = (mu[j0:j1]*energy[j0:j1]).mean()/energy[j0:j1].mean() 

172 mu_out.append(val) 

173 err_out.append(mu[j0:j1].std()) 

174 j0 = j1 

175 

176 newname = group.__name__ + '_rebinned' 

177 group.rebinned = Group(energy=np.array(en), mu=np.array(mu_out), 

178 delta_mu=np.array(err_out), e0=e0, 

179 __name__=newname) 

180 return