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