[b9d5f88] | 1 | """ |
---|
[aa36f96] | 2 | This file is intended to be a temporary file to communicate in-progress code |
---|
| 3 | to the developers. |
---|
| 4 | This file should be removed after its content has been used by the team. |
---|
[b9d5f88] | 5 | """ |
---|
| 6 | |
---|
| 7 | |
---|
| 8 | # This code belongs in AbstractFitEngine |
---|
| 9 | class FitData1D: |
---|
| 10 | def setFitRange(self,qmin=None,qmax=None): |
---|
| 11 | """ |
---|
[aa36f96] | 12 | Change the fit range. |
---|
| 13 | Take into account the fact that if smearing is applied, |
---|
| 14 | a wider range in unsmeared Q might be necessary to cover |
---|
| 15 | the smeared (observed) Q range. |
---|
[b9d5f88] | 16 | """ |
---|
| 17 | |
---|
| 18 | # Skip Q=0 point, (especially for y(q=0)=None at x[0]). |
---|
| 19 | #ToDo: Fix this. |
---|
| 20 | if qmin==0.0 and not numpy.isfinite(self.data.y[qmin]): |
---|
| 21 | self.qmin = min(self.data.x[self.data.x!=0]) |
---|
| 22 | elif qmin!=None: |
---|
| 23 | self.qmin = qmin |
---|
| 24 | |
---|
| 25 | if qmax !=None: |
---|
| 26 | self.qmax = qmax |
---|
| 27 | |
---|
| 28 | # Range used for input to smearing |
---|
| 29 | self._qmin_unsmeared = self.qmin |
---|
| 30 | self._qmax_unsmeared = self.qmax |
---|
| 31 | |
---|
| 32 | # Determine the range needed in unsmeared-Q to cover |
---|
| 33 | # the smeared Q range |
---|
| 34 | if self.smearer.__class__.__name__ == 'SlitSmearer': |
---|
| 35 | # The entries in the slit smearer matrix remain |
---|
| 36 | # large across all bins, so we keep the full Q range. |
---|
| 37 | self._qmin_unsmeared = min(self.data.x) |
---|
| 38 | self._qmax_unsmeared = max(self.data.x) |
---|
| 39 | elif self.smearer.__class__.__name__ == 'QSmearer': |
---|
| 40 | # Take 3 sigmas as the offset between smeared and unsmeared space. |
---|
| 41 | try: |
---|
| 42 | offset = 3.0*max(self.smearer.width) |
---|
| 43 | self._qmin_unsmeared = max([min(self.data.x), self.qmin-offset]) |
---|
| 44 | self._qmax_unsmeared = min([max(self.data.x), self.qmax+offset]) |
---|
| 45 | except: |
---|
| 46 | logging.error("FitData1D.setFitRange: %s" % sys.exc_value) |
---|
| 47 | |
---|
| 48 | |
---|
| 49 | def residuals(self, fn): |
---|
| 50 | """ |
---|
[aa36f96] | 51 | Compute residuals. |
---|
| 52 | |
---|
| 53 | If self.smearer has been set, use if to smear |
---|
| 54 | the data before computing chi squared. |
---|
| 55 | |
---|
| 56 | This is a version based on the current version of residuals. |
---|
| 57 | |
---|
| 58 | It takes into account the fact that the unsmearing range |
---|
| 59 | might need to be wider than the smeared (observed) range. |
---|
| 60 | |
---|
| 61 | :param fn: function that return model value |
---|
| 62 | |
---|
| 63 | :return: residuals |
---|
| 64 | |
---|
[b9d5f88] | 65 | """ |
---|
| 66 | x,y = [numpy.asarray(v) for v in (self.x,self.y)] |
---|
| 67 | if self.dy ==None or self.dy==[]: |
---|
| 68 | dy= numpy.zeros(len(y)) |
---|
| 69 | else: |
---|
| 70 | dy= numpy.asarray(dy) |
---|
| 71 | |
---|
| 72 | dy[dy==0]=1 |
---|
| 73 | idx_unsmeared = (x>=self._qmin_unsmeared) & (x <= self._qmax_unsmeared) |
---|
| 74 | |
---|
| 75 | # Compute theory data f(x) |
---|
| 76 | idx=[] |
---|
| 77 | tempy=[] |
---|
| 78 | tempfx=[] |
---|
| 79 | tempdy=[] |
---|
| 80 | |
---|
| 81 | _first_bin = None |
---|
| 82 | for i_x in range(len(x)): |
---|
| 83 | try: |
---|
| 84 | if idx_unsmeared[i_x]==True: |
---|
| 85 | if _first_bin is None: |
---|
| 86 | _first_bin = i_x |
---|
| 87 | |
---|
| 88 | value= fn(x[i_x]) |
---|
| 89 | idx.append(x[i_x]>=self.qmin and x[i_x]<=self.qmax) |
---|
| 90 | tempfx.append( value) |
---|
| 91 | tempy.append(y[i_x]) |
---|
| 92 | tempdy.append(dy[i_x]) |
---|
| 93 | except: |
---|
| 94 | ## skip error for model.run(x) |
---|
| 95 | pass |
---|
| 96 | |
---|
| 97 | ## Smear theory data |
---|
| 98 | # The tempfx array has a length limited by the Q range. |
---|
| 99 | if self.smearer is not None: |
---|
| 100 | tempfx = self.smearer(tempfx, _first_bin) |
---|
| 101 | |
---|
| 102 | newy = numpy.asarray(tempy) |
---|
| 103 | newfx= numpy.asarray(tempfx) |
---|
| 104 | newdy= numpy.asarray(tempdy) |
---|
| 105 | |
---|
| 106 | ## Sanity check |
---|
| 107 | if numpy.size(newdy)!= numpy.size(newfx): |
---|
| 108 | raise RuntimeError, "FitData1D: invalid error array %d <> %d" % (numpy.size(newdy), numpy.size(newfx)) |
---|
| 109 | |
---|
| 110 | return (newy[idx]-newfx[idx])/newdy[idx] |
---|
| 111 | |
---|
| 112 | |
---|
| 113 | def residuals_alt(self, fn): |
---|
| 114 | """ |
---|
[aa36f96] | 115 | Compute residuals. |
---|
| 116 | |
---|
| 117 | If self.smearer has been set, use if to smear |
---|
| 118 | the data before computing chi squared. |
---|
| 119 | |
---|
| 120 | This is a more streamlined version of the above. To use this version, |
---|
| 121 | the _BaseSmearer class below needs to be modified to have its __call__ |
---|
| 122 | method have the following signature: |
---|
| 123 | |
---|
| 124 | __call__(self, iq, first_bin, last_bin) |
---|
| 125 | |
---|
| 126 | This is because we are storing results in arrays of a length |
---|
| 127 | corresponding to the full Q-range. |
---|
| 128 | |
---|
| 129 | It takes into account the fact that the unsmearing range |
---|
| 130 | might need to be wider than the smeared (observed) range. |
---|
| 131 | |
---|
| 132 | :param fn: function that return model value |
---|
| 133 | |
---|
| 134 | :return: residuals |
---|
| 135 | |
---|
[b9d5f88] | 136 | """ |
---|
| 137 | # Make sure the arrays are numpy arrays, which are |
---|
| 138 | # expected by the fitter. |
---|
| 139 | x,y = [numpy.asarray(v) for v in (self.x,self.y)] |
---|
| 140 | if self.dy ==None or self.dy==[]: |
---|
| 141 | dy= numpy.zeros(len(y)) |
---|
| 142 | else: |
---|
| 143 | dy= numpy.asarray(dy) |
---|
| 144 | |
---|
| 145 | dy[dy==0]=1 |
---|
| 146 | idx = (x>=self.qmin) & (x <= self.qmax) |
---|
| 147 | idx_unsmeared = (x>=self._qmin_unsmeared) & (x <= self._qmax_unsmeared) |
---|
| 148 | |
---|
| 149 | # Compute theory data f(x) |
---|
| 150 | fx= numpy.zeros(len(x)) |
---|
| 151 | |
---|
| 152 | # First and last bins of the array, corresponding to |
---|
| 153 | # the Q range to be smeared |
---|
| 154 | _first_bin = None |
---|
| 155 | _last_bin = None |
---|
| 156 | for i_x in range(len(x)): |
---|
| 157 | try: |
---|
| 158 | if idx_unsmeared[i_x]==True: |
---|
| 159 | if _first_bin is None: |
---|
| 160 | _first_bin = i_x |
---|
| 161 | else: |
---|
| 162 | _last_bin = i_x |
---|
| 163 | |
---|
| 164 | value = fn(x[i_x]) |
---|
| 165 | fx[i_x] = value |
---|
| 166 | except: |
---|
| 167 | ## skip error for model.run(x) |
---|
| 168 | ## Should properly log the error |
---|
| 169 | pass |
---|
| 170 | |
---|
| 171 | # Smear theory data |
---|
| 172 | if self.smearer is not None: |
---|
| 173 | fx = self.smearer(fx, _first_bin, _last_bin) |
---|
| 174 | |
---|
| 175 | # Sanity check |
---|
| 176 | if numpy.size(dy)!= numpy.size(fx): |
---|
| 177 | raise RuntimeError, "FitData1D: invalid error array %d <> %d" % (numpy.size(dy), numpy.size(fx)) |
---|
| 178 | |
---|
| 179 | # Return the residuals for the smeared (observed) Q range |
---|
| 180 | return (y[idx]-fx[idx])/dy[idx] |
---|
| 181 | |
---|
| 182 | # The following code belongs in DataLoader.qsmearing |
---|
| 183 | class _BaseSmearer(object): |
---|
| 184 | |
---|
| 185 | def __init__(self): |
---|
| 186 | self.nbins = 0 |
---|
| 187 | self._weights = None |
---|
| 188 | |
---|
| 189 | def _compute_matrix(self): return NotImplemented |
---|
| 190 | |
---|
| 191 | def __call__(self, iq, first_bin=0): |
---|
| 192 | """ |
---|
[aa36f96] | 193 | Return the smeared I(q) value at the given q. |
---|
| 194 | The smeared I(q) is computed using a predetermined |
---|
| 195 | smearing matrix for a particular binning. |
---|
| 196 | |
---|
| 197 | :param q: I(q) array |
---|
| 198 | :param first_bin: first bin of the given iq array if shorter than full data length |
---|
| 199 | |
---|
| 200 | :return: smeared I(q) |
---|
[b9d5f88] | 201 | |
---|
| 202 | """ |
---|
| 203 | # Sanity check |
---|
| 204 | if len(iq)+first_bin > self.nbins: |
---|
| 205 | raise RuntimeError, "Invalid I(q) vector: inconsistent array length %s > %s" % (str(len(iq)+first_bin), str(self.nbins)) |
---|
| 206 | |
---|
| 207 | if self._weights == None: |
---|
| 208 | self._compute_matrix() |
---|
| 209 | |
---|
| 210 | iq_smeared = numpy.zeros(len(iq)) |
---|
| 211 | # Loop over q-values |
---|
| 212 | idwb=[] |
---|
| 213 | |
---|
| 214 | for q_i in range(len(iq)): |
---|
| 215 | sum = 0.0 |
---|
| 216 | counts = 0.0 |
---|
| 217 | for i in range(len(iq)): |
---|
| 218 | if iq[i]==0 or self._weights[q_i+first_bin][i+first_bin]==0: |
---|
| 219 | continue |
---|
| 220 | else: |
---|
| 221 | sum += iq[i] * self._weights[q_i+first_bin][i+first_bin] |
---|
| 222 | counts += self._weights[q_i+first_bin][i+first_bin] |
---|
| 223 | |
---|
| 224 | if counts == 0: |
---|
| 225 | iq_smeared[q_i] = 0 |
---|
| 226 | else: |
---|
| 227 | iq_smeared[q_i] = sum/counts |
---|
| 228 | return iq_smeared |
---|