|
| 1 | +import numpy as np |
| 2 | +from numba import njit |
| 3 | + |
| 4 | +@njit |
| 5 | +def arx(dat,sig, nloop=1000,verbose=False): |
| 6 | + """ |
| 7 | + arx(data, sig, nloop=100) |
| 8 | +
|
| 9 | + Routine to calculate the average and additional |
| 10 | + scatter on a dataset |
| 11 | +
|
| 12 | + Parameters |
| 13 | + ---------- |
| 14 | + data : float array |
| 15 | + Data array |
| 16 | + sig : float |
| 17 | + 1-sigma uncertainties on the data array |
| 18 | + nloop : int, optional |
| 19 | + Number of maximum iterations. Default=10000 |
| 20 | + verbose : logical, optional |
| 21 | + Logical flag to print debugging statements. Default=False |
| 22 | +
|
| 23 | + Returns |
| 24 | + ---------- |
| 25 | + avg : float |
| 26 | + Average of the data |
| 27 | + avg_err: float |
| 28 | + 1-sigma errorbar on the average |
| 29 | + sig0 : float |
| 30 | + Additional scatter |
| 31 | + sig0: float |
| 32 | + 1-sigma errorbar on the scatter |
| 33 | +
|
| 34 | + References |
| 35 | + ---------- |
| 36 | + .. [1] Based on avgrmsx.for written by Keith Horne |
| 37 | +
|
| 38 | + Examples |
| 39 | + -------- |
| 40 | + Calculate the average of a dataset |
| 41 | +
|
| 42 | + >>> |
| 43 | + >>> avgo = 0, 0.1 # mean and standard deviation |
| 44 | + """ |
| 45 | + ################### |
| 46 | + |
| 47 | + ############ |
| 48 | + ng = len(dat) |
| 49 | + |
| 50 | + |
| 51 | + |
| 52 | + |
| 53 | + avg = 0. |
| 54 | + rms = 0. |
| 55 | + e1=0. |
| 56 | + sum1=0.0 |
| 57 | + sum2=0. |
| 58 | + # mean of positive error bars |
| 59 | + for i in range(ng): |
| 60 | + e1 = e1 + sig[i] |
| 61 | + e1 = e1/ng |
| 62 | + |
| 63 | + #initial estimate |
| 64 | + for i in range(ng): |
| 65 | + w = (e1 / sig[i])**2 |
| 66 | + sum1 = sum1 + dat[i] * w |
| 67 | + sum2 = sum2 + w |
| 68 | + |
| 69 | + # optimal average and its variance |
| 70 | + avg = sum1 / sum2 |
| 71 | + sigavg = e1 / ( sum2**0.5 ) |
| 72 | + # scale factor ( makes <1/sig^2>=1 ) |
| 73 | + w = ng / sum2 |
| 74 | + e1 = e1 * ( w **0.5) |
| 75 | + # estimate for rms |
| 76 | + rms = e1 |
| 77 | + |
| 78 | + # convergence threshold |
| 79 | + tiny = 2.0e-5 |
| 80 | + #* lower limit for extra rms |
| 81 | + rmin = tiny * e1 |
| 82 | + |
| 83 | + ## KDH : NO LONGER NEEDED ? |
| 84 | + #if rmin < 0.: |
| 85 | + # print('** WARNING in AVGRMSX : rmin not positive :(') |
| 86 | + # print(' sum1', sum1, ' sum2', sum2, ' ng', ng) |
| 87 | + # print(' avg', avg, ' sigavg', sigavg) |
| 88 | + # print(' e1', e1, ' rmin', rmin) |
| 89 | + |
| 90 | + for loop in range(nloop): |
| 91 | + oldavg = avg |
| 92 | + oldrms = rms |
| 93 | + # resetting the partial sums. Bug corrected on 26/10/2023 |
| 94 | + sum1 = 0.0 |
| 95 | + sum2 = 0.0 |
| 96 | + sumg = 0.0 |
| 97 | + sumo = 0.0 |
| 98 | + sum3 = 0.0 |
| 99 | + #* scaled avg and rms |
| 100 | + a = avg / e1 |
| 101 | + r = rms / e1 |
| 102 | + rr = r * r |
| 103 | + for i in range(ng): |
| 104 | + #* scale data and error bar |
| 105 | + d = dat[i] / e1 |
| 106 | + e = sig[i] / e1 |
| 107 | + |
| 108 | + #* "goodness" of this data point |
| 109 | + g = rr / ( rr + e * e ) |
| 110 | + x = g * ( d - a ) |
| 111 | + xx = x * x |
| 112 | + |
| 113 | + #* increment sums |
| 114 | + |
| 115 | + sum1 = sum1 + g * d |
| 116 | + sumg = sumg + g |
| 117 | + sumo = sumo + g * g |
| 118 | + sum2 = sum2 + xx |
| 119 | + sum3 = sum3 + g * xx |
| 120 | + |
| 121 | + #* "good" data points ( e.g. with sig < rms ) |
| 122 | + good = sumg |
| 123 | + |
| 124 | + if sumg <= 0.0: |
| 125 | + if verbose: print('ERROR in AVGRMSX. NON-POSITIVE SUM(G)=', sumg) |
| 126 | + break |
| 127 | + |
| 128 | + #* revise avg and rms |
| 129 | + a = sum1 / sumg |
| 130 | + r = ( sum2**0.5 ) / ( sumg** 0.5 ) |
| 131 | + r = np.max( np.array([rmin, r] )) |
| 132 | + |
| 133 | + #* error bars on scaled avg and rms |
| 134 | + siga = r / ( sumg**0.5 ) |
| 135 | + gg = 2.0 * ( 2.0 * sum3 / sum2 * sumg - sumo ) |
| 136 | + sigr = r |
| 137 | + if gg > 0.0: sigr = r / ( gg**0.5) |
| 138 | + |
| 139 | + #* restore scaling |
| 140 | + avg = a * e1 |
| 141 | + rms = r * e1 |
| 142 | + sigavg = siga * e1 |
| 143 | + sigrms = sigr * e1 |
| 144 | + |
| 145 | + #* KDH: CATCH SUM2=0 (NO VARIANCE) |
| 146 | + if sum2 <= 0.0: |
| 147 | + rms = 0.0 |
| 148 | + sigrms = -1 |
| 149 | + if verbose: print("NO VARIANCE") |
| 150 | + break |
| 151 | + |
| 152 | + #* converge when test < 1 |
| 153 | + if loop > 1: |
| 154 | + #step |
| 155 | + safe = 0.9 |
| 156 | + avg = oldavg * (1.0 - safe) + safe * avg |
| 157 | + rms = oldrms * ( 1. - safe ) + safe * rms |
| 158 | + # chi |
| 159 | + chiavg = ( avg - oldavg ) / sigavg |
| 160 | + chirms = ( rms - oldrms ) / sigrms |
| 161 | + test = np.max( np.array([np.abs( chiavg ), np.abs( chirms ) ])) / tiny |
| 162 | + |
| 163 | + |
| 164 | + #if loop > nloop - 4: |
| 165 | + # print('Loop', loop, ' of', nloop, ' in AVGRMSX') |
| 166 | + # print('Ndat', n, ' Ngood', good, ' Neff', g) |
| 167 | + # print(' avg', avg, ' rms', rms) |
| 168 | + # print(' +/-', sigavg, ' +/-', sigrms) |
| 169 | + # print(' chiavg', chiavg, ' chirms', chirms, ' test', test) |
| 170 | + # if test < 1.: print('** CONVERGED ** :))') |
| 171 | + # Converged |
| 172 | + if test < 1.0: |
| 173 | + if verbose: print('** CONVERGED ** :))', loop) |
| 174 | + break |
| 175 | + |
| 176 | + # quit if rms vanishes |
| 177 | + if rms <= 0.0: break |
| 178 | + if verbose: print('** Reached end of iteration loop ** :(', loop) |
| 179 | + return avg,sigavg,rms,sigrms |
| 180 | + |
0 commit comments