[3570545] | 1 | /* This program is public domain. */ |
---|
| 2 | |
---|
| 3 | #include <stdio.h> |
---|
| 4 | #include <assert.h> |
---|
| 5 | #include <string.h> |
---|
| 6 | #include <math.h> |
---|
| 7 | #ifdef SGI |
---|
| 8 | #include <ieeefp.h> |
---|
| 9 | #endif |
---|
| 10 | |
---|
| 11 | /* What to do at the endpoints --- USE_TRUNCATED_NORMALIZATION will |
---|
| 12 | * avoid the assumption that the data is zero where it hasn't been |
---|
| 13 | * measured. |
---|
| 14 | */ |
---|
| 15 | #define USE_TRUNCATED_NORMALIZATION |
---|
| 16 | |
---|
| 17 | /* Computed using extended precision with Octave's symbolic toolbox. */ |
---|
| 18 | #define PI4 12.56637061435917295385 |
---|
| 19 | #define PI_180 0.01745329251994329576 |
---|
| 20 | #define LN256 5.54517744447956247533 |
---|
| 21 | #define SQRT2 1.41421356237309504880 |
---|
| 22 | #define SQRT2PI 2.50662827463100050241 |
---|
| 23 | |
---|
| 24 | /* Choose the resolution limit based on relative contribution to resolution |
---|
| 25 | * rather than absolute contribution. So if we ignore everything below, |
---|
| 26 | * e.g. 0.1% of the peak, that limit occurs when G(x)/G(0) = 0.001 for |
---|
| 27 | * gaussian G of width sima, or when x = sqrt(-2 sigma^2 log(0.001)). */ |
---|
| 28 | #define LOG_RESLIMIT -6.90775527898213703123 |
---|
| 29 | |
---|
| 30 | /** \file |
---|
| 31 | The resolution function returns the convolution of the reflectometry |
---|
| 32 | curve with a Q-dependent gaussian. |
---|
| 33 | |
---|
| 34 | We provide the following function: |
---|
| 35 | resolution(Nin, Qin, Rin, N, Q, dQ, R) returns convolution |
---|
| 36 | |
---|
| 37 | where |
---|
| 38 | Nin is the number of theory points |
---|
| 39 | Qin,Rin are the computed theory points |
---|
| 40 | N is the number of Q points to calculate |
---|
| 41 | Q are the locations of the measured data points |
---|
| 42 | dQ are the width (sigma) of the convolution at each measured point |
---|
| 43 | R is the returned convolution. |
---|
| 44 | |
---|
| 45 | Note that FWHM = sqrt(8 ln 2) dQ, so scale dQ appropriately. |
---|
| 46 | |
---|
| 47 | The contribution of Q to a resolution of width dQo at point Qo is: |
---|
| 48 | |
---|
| 49 | p(Q) = 1/sqrt(2 pi dQo^2) exp ( (Q-Qo)^2/(2 dQo^2) ) |
---|
| 50 | |
---|
| 51 | We are approximating the convolution at Qo using a numerical |
---|
| 52 | approximation to the integral over the measured points. For |
---|
| 53 | efficiency, the integral is limited to p(Q_i)/p(0)>=0.001. |
---|
| 54 | |
---|
| 55 | Note that the function we are convoluting is falling off as Q^4. |
---|
| 56 | That means the correct convolution should uniformly sample across |
---|
| 57 | the entire width of the Gaussian. This is not possible at the |
---|
| 58 | end points unless you calculate the reflectivity beyond what is |
---|
| 59 | strictly needed for the data. For a given dQ and step size, |
---|
| 60 | you need enough steps that the following is true: |
---|
| 61 | |
---|
| 62 | (n*step)^2 > -2 dQ^2 * ln 0.001 |
---|
| 63 | |
---|
| 64 | The choice of sampling density is particularly important near the |
---|
| 65 | critical edge. This is where the resolution calculation has the |
---|
| 66 | largest effect on the reflectivity curve. In one particular model, |
---|
| 67 | calculating every 0.001 rather than every 0.02 changed one value |
---|
| 68 | above the critical edge by 15%. This is likely to be a problem for |
---|
| 69 | any system with a well defined critical edge. The solution is to |
---|
| 70 | compute the theory function over a finer mesh where the derivative |
---|
| 71 | is changing rapidly. For the critical edge, I have found a sampling |
---|
| 72 | density of 0.005 to be good enough. |
---|
| 73 | |
---|
| 74 | For systems involving thick layers, the theory function oscillates |
---|
| 75 | rapidly around the measured points. This is a problem when the |
---|
| 76 | period of the oscillation, 2 pi/d for total sample depth d, is on |
---|
| 77 | the order of the width of the resolution function. This is true even |
---|
| 78 | for gradually changing profiles in materials with very high roughness |
---|
| 79 | values. In these systems, the theory function should be oversampled |
---|
| 80 | around the measured points Q. With a single thick layer, oversampling |
---|
| 81 | can be limited to just one period. With multiple thick layers, |
---|
| 82 | oscillations will show interference patterns and it will be necessary |
---|
| 83 | to oversample uniformly between the measured points. When oversampled |
---|
| 84 | spacing is less than about 2 pi/7 d, it is possible to see aliasing |
---|
| 85 | effects. |
---|
| 86 | |
---|
| 87 | FIXME is it better to use random sampling or strictly |
---|
| 88 | regular spacing when you are undersampling? |
---|
| 89 | |
---|
| 90 | =============================================================== |
---|
| 91 | */ |
---|
| 92 | |
---|
| 93 | #undef USE_TRAPEZOID_RULE |
---|
| 94 | #ifdef USE_TRAPEZOID_RULE |
---|
| 95 | #warning This code does strange things with small sigma and large spacing |
---|
| 96 | /* FIXME trapezoid performs very badly for large spacing unless |
---|
| 97 | we normalize to the unit width. For very small sigma, the gaussian |
---|
| 98 | is a spike, but we are approximating it by a triangle so it is |
---|
| 99 | not surprising it works so poorly. A slightly better solution is |
---|
| 100 | to use the inner limits rather than the outer limits, but this will |
---|
| 101 | still break down if the Q spacing is approximately equal to limit. |
---|
| 102 | Best is to not use trapezoid. |
---|
| 103 | */ |
---|
| 104 | |
---|
| 105 | /* Trapezoid rule for numerical integration of convolution */ |
---|
| 106 | double |
---|
| 107 | convolve_point(const double Qin[], const double Rin[], int k, int n, |
---|
| 108 | double Qo, double limit, double sigma) |
---|
| 109 | { |
---|
| 110 | const double two_sigma_sq = 2. * sigma * sigma; |
---|
| 111 | double z, Glo, RGlo, R, norm; |
---|
| 112 | |
---|
| 113 | z = Qo - Qin[k]; |
---|
| 114 | Glo = exp(-z*z/two_sigma_sq); |
---|
| 115 | RGlo = Rin[k]*Glo; |
---|
| 116 | norm = R = 0.; |
---|
| 117 | while (++k < n) { |
---|
| 118 | /* Compute the next endpoint */ |
---|
| 119 | const double zhi = Qo - Qin[k]; |
---|
| 120 | const double Ghi = exp(-zhi*zhi/two_sigma_sq); |
---|
| 121 | const double RGhi = Rin[k] * Ghi; |
---|
| 122 | const double halfstep = 0.5*(Qin[k] - Qin[k-1]); |
---|
| 123 | |
---|
| 124 | /* Add the trapezoidal area. */ |
---|
| 125 | norm += halfstep * (Ghi + Glo); |
---|
| 126 | R += halfstep * (RGhi + RGlo); |
---|
| 127 | |
---|
| 128 | /* Save the endpoint for next trapezoid. */ |
---|
| 129 | Glo = Ghi; |
---|
| 130 | RGlo = RGhi; |
---|
| 131 | |
---|
| 132 | /* Check if we've calculated far enough */ |
---|
| 133 | if (Qin[k] >= Qo+limit) break; |
---|
| 134 | } |
---|
| 135 | |
---|
| 136 | /* Scale to area of the linear spline distribution we actually used. */ |
---|
| 137 | return R / norm; |
---|
| 138 | |
---|
| 139 | /* Scale to gaussian of unit area */ |
---|
| 140 | /* Fails badly for small sigma or large Q steps---do not use. */ |
---|
| 141 | /* return R / sigma*SQRT2PI; */ |
---|
| 142 | } |
---|
| 143 | |
---|
| 144 | #else /* !USE_TRAPEZOID_RULE */ |
---|
| 145 | |
---|
| 146 | /* Analytic convolution of gaussian with linear spline */ |
---|
| 147 | /* More expensive but more reliable */ |
---|
| 148 | double |
---|
| 149 | convolve_point(const double Qin[], const double Rin[], int k, int n, |
---|
| 150 | double Qo, double limit, double sigma) |
---|
| 151 | { |
---|
| 152 | const double two_sigma_sq = 2. * sigma * sigma; |
---|
| 153 | double z, Glo, erflo, erfmin, R; |
---|
| 154 | |
---|
| 155 | z = Qo - Qin[k]; |
---|
| 156 | Glo = exp(-z*z/two_sigma_sq); |
---|
| 157 | erfmin = erflo = erf(-z/(SQRT2*sigma)); |
---|
| 158 | R = 0.; |
---|
| 159 | /* printf("%5.3f: (%5.3f,%11.5g)",Qo,Qin[k],Rin[k]); */ |
---|
| 160 | while (++k < n) { |
---|
| 161 | /* No additional contribution from duplicate points. */ |
---|
| 162 | if (Qin[k] == Qin[k-1]) continue; |
---|
| 163 | |
---|
| 164 | /* Compute the next endpoint */ |
---|
| 165 | const double zhi = Qo - Qin[k]; |
---|
| 166 | const double Ghi = exp(-zhi*zhi/two_sigma_sq); |
---|
| 167 | const double erfhi = erf(-zhi/(SQRT2*sigma)); |
---|
| 168 | const double m = (Rin[k]-Rin[k-1])/(Qin[k]-Qin[k-1]); |
---|
| 169 | const double b = Rin[k] - m * Qin[k]; |
---|
| 170 | |
---|
| 171 | /* Add the integrals. */ |
---|
| 172 | R += 0.5*(m*Qo+b)*(erfhi-erflo) - sigma/SQRT2PI*m*(Ghi-Glo); |
---|
| 173 | |
---|
| 174 | /* Debug computation failures. */ |
---|
| 175 | /* |
---|
| 176 | if isnan(R) { |
---|
| 177 | printf("NaN from %d: zhi=%g, Ghi=%g, erfhi=%g, m=%g, b=%g\n", |
---|
| 178 | k,zhi,Ghi,erfhi,m,b); |
---|
| 179 | } |
---|
| 180 | */ |
---|
| 181 | |
---|
| 182 | /* Save the endpoint for next trapezoid. */ |
---|
| 183 | Glo = Ghi; |
---|
| 184 | erflo = erfhi; |
---|
| 185 | |
---|
| 186 | /* Check if we've calculated far enough */ |
---|
| 187 | if (Qin[k] >= Qo+limit) break; |
---|
| 188 | } |
---|
| 189 | /* printf(" (%5.3f,%11.5g)",Qin[k<n?k:n-1],Rin[k<n?k:n-1]); */ |
---|
| 190 | |
---|
| 191 | #ifdef USE_TRUNCATED_NORMALIZATION |
---|
| 192 | /* Normalize by the area of the truncated gaussian */ |
---|
| 193 | /* At this point erflo = erfmax */ |
---|
| 194 | /* printf ("---> %11.5g\n",2*R/(erflo-erfmin)); */ |
---|
| 195 | return 2 * R / (erflo - erfmin); |
---|
| 196 | #else |
---|
| 197 | /* Return unnormalized (we used a gaussian of unit area) */ |
---|
| 198 | /* printf ("---> %11.5g\n",R); */ |
---|
| 199 | return R; |
---|
| 200 | #endif |
---|
| 201 | } |
---|
| 202 | |
---|
| 203 | #endif /* !USE_TRAPEZOID_RULE */ |
---|
| 204 | |
---|
| 205 | void |
---|
| 206 | resolution(int Nin, const double Qin[], const double Rin[], |
---|
| 207 | int N, const double Q[], const double dQ[], double R[]) |
---|
| 208 | { |
---|
| 209 | int lo,out; |
---|
| 210 | |
---|
| 211 | /* FIXME fails if Qin are not sorted; slow if Q not sorted */ |
---|
| 212 | assert(Nin>1); |
---|
| 213 | |
---|
| 214 | /* Scan through all Q values to be calculated */ |
---|
| 215 | lo = 0; |
---|
| 216 | for (out=0; out < N; out++) { |
---|
| 217 | /* width of resolution window for Q is w = 2 dQ^2. */ |
---|
| 218 | const double sigma = dQ[out]; |
---|
| 219 | const double Qo = Q[out]; |
---|
| 220 | const double limit = sqrt(-2.*sigma*sigma* LOG_RESLIMIT); |
---|
| 221 | |
---|
| 222 | /* if (out%20==0) printf("%d: Q,dQ = %g,%g\n",out,Qo,sigma); */ |
---|
| 223 | |
---|
| 224 | /* Line up the left edge of the convolution window */ |
---|
| 225 | /* It is probably forward from the current position, */ |
---|
| 226 | /* but if the next dQ is a lot higher than the current */ |
---|
| 227 | /* dQ or if the Q are not sorted, then it may be before */ |
---|
| 228 | /* the current position. */ |
---|
| 229 | /* FIXME verify that the convolution window is just right */ |
---|
| 230 | while (lo < Nin-1 && Qin[lo] < Qo-limit) lo++; |
---|
| 231 | while (lo > 0 && Qin[lo] > Qo-limit) lo--; |
---|
| 232 | |
---|
| 233 | /* Special handling to avoid 0/0 for w=0. */ |
---|
| 234 | if (sigma > 0.) { |
---|
| 235 | R[out] = convolve_point(Qin,Rin,lo,Nin,Qo,limit,sigma); |
---|
| 236 | } else if (lo < Nin-1) { |
---|
| 237 | /* Linear interpolation */ |
---|
| 238 | double m = (Rin[lo+1]-Rin[lo])/(Qin[lo+1]-Qin[lo]); |
---|
| 239 | double b = Rin[lo] - m*Qin[lo]; |
---|
| 240 | R[out] = m*Qo + b; |
---|
| 241 | } else if (lo > 0) { |
---|
| 242 | /* Linear extrapolation */ |
---|
| 243 | double m = (Rin[lo]-Rin[lo-1])/(Qin[lo]-Qin[lo-1]); |
---|
| 244 | double b = Rin[lo] - m*Qin[lo]; |
---|
| 245 | R[out] = m*Qo + b; |
---|
| 246 | } else { |
---|
| 247 | /* Can't happen because there is more than one point in Qin. */ |
---|
| 248 | assert(Nin>1); |
---|
| 249 | } |
---|
| 250 | } |
---|
| 251 | |
---|
| 252 | } |
---|