PageRenderTime 23ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 1ms

/refl1d/lib/contract_profile.cc

https://github.com/reflectometry/refl1d
C++ | 435 lines | 293 code | 46 blank | 96 comment | 77 complexity | e8509741e1a627641421712f1f3aa9ee MD5 | raw file
  1. // This program is public domain.
  2. // Force DEBUG mode with assertions tested.
  3. //#ifdef NDEBUG
  4. //#undef NDEBUG
  5. //#endif
  6. #define _USE_MATH_DEFINES
  7. #include <cmath>
  8. #include <iostream>
  9. #include <algorithm>
  10. #define GREEDY
  11. #include <cassert>
  12. #define Z_EPS 1e-6
  13. typedef double FullLayer[6];
  14. extern "C"
  15. int
  16. align_magnetic(int nlayers, double d[], double sigma[], double rho[], double irho[],
  17. int nlayersM, double dM[], double sigmaM[], double rhoM[], double thetaM[],
  18. int noutput, double output_flat[])
  19. {
  20. // ignoring thickness d on the first and last layers
  21. // ignoring interface width sigma on the last layer
  22. // making sure there are at least two layers
  23. assert(nlayers>1);
  24. assert(nlayersM>1);
  25. FullLayer *output = (FullLayer *)output_flat;
  26. int magnetic = 0; // current magnetic layer index
  27. int nuclear = 0; // current nuclear layer index
  28. double z = 0.; // current interface depth
  29. double next_z = 0.; // next nuclear interface
  30. double next_zM = 0.; // next magnetic interface
  31. //int active = 3; // active interfaces, active&0x1 for nuclear, active&0x2 for magnetic
  32. int k = 0; // current output layer index
  33. while (1) { // repeat over all nuclear/magnetic layers
  34. if (k == noutput) return -1; // exceeds capacity of output
  35. assert(nuclear < nlayers);
  36. assert(magnetic < nlayersM);
  37. //printf("%d: %d %d %g %g %g\n", k, nuclear, magnetic, z, next_z, next_zM);
  38. //printf("%g %g %g %g\n", rho[nuclear], irho[nuclear], rhoM[magnetic], thetaM[magnetic]);
  39. //printf("%g %g %g %g\n", d[nuclear], sigma[nuclear], dM[magnetic], sigmaM[magnetic]);
  40. // Set the scattering strength using the current parameters
  41. output[k][2] = rho[nuclear];
  42. output[k][3] = irho[nuclear];
  43. output[k][4] = rhoM[magnetic];
  44. output[k][5] = thetaM[magnetic];
  45. // Check if we are at the last layer for both nuclear and magnetic
  46. // If so set thickness and interface width to zero. We are doing a
  47. // center of the loop exit in order to make sure that the final layer
  48. // is added.
  49. if (magnetic == nlayersM-1 && nuclear == nlayers-1) {
  50. output[k][0] = 0.;
  51. output[k][1] = 0.;
  52. k++;
  53. break;
  54. }
  55. // Determine if we are adding the nuclear or the magnetic interface next,
  56. // or possibly both. The order of the conditions is important.
  57. //
  58. // Note: the final value for next_z/next_zM is not defined. Rather than
  59. // checking if we are on the last layer we simply add the value of the
  60. // last thickness to z, which may be 0, nan, inf, or anything else. This
  61. // doesn't affect the algorithm since we don't look at next_z when we are
  62. // on the final nuclear layer or next_zM when we are on the final magnetic
  63. // layer.
  64. //
  65. // Note: averaging nearly aligned interfaces can lead to negative thickness
  66. // Consider nuc = [1-a, 0, 1] and mag = [1+a, 1, 1] for 2a < Z_EPS.
  67. // On the first step we set next_z to 1-a, next_zM to 1+a and z to the
  68. // average of 1-a and 1+a, which is 1. On the second step next_z is
  69. // still 1-a, so the thickness next_z - z = -a. Since a is tiny we can just
  70. // pretend that -a == zero by setting thickness to fmax(next_z - z, 0.0).
  71. //
  72. if (nuclear == nlayers-1) {
  73. // No more nuclear layers... play out the remaining magnetic layers.
  74. output[k][0] = std::max(next_zM - z, 0.0);
  75. output[k][1] = sigmaM[magnetic];
  76. next_zM += dM[++magnetic];
  77. } else if (magnetic == nlayersM-1) {
  78. // No more magnetic layers... play out the remaining nuclear layers.
  79. output[k][0] = std::max(next_z - z, 0.0);
  80. output[k][1] = sigma[nuclear];
  81. next_z += d[++nuclear];
  82. } else if (fabs(next_z - next_zM) < Z_EPS && fabs(sigma[nuclear]-sigmaM[magnetic]) < Z_EPS) {
  83. // Matching nuclear/magnetic boundary, with almost identical interfaces.
  84. // Increment both nuclear and magnetic layers.
  85. output[k][0] = std::max(0.5*(next_z + next_zM) - z, 0.0);
  86. output[k][1] = 0.5*(sigma[nuclear] + sigmaM[magnetic]);
  87. next_z += d[++nuclear];
  88. next_zM += dM[++magnetic];
  89. } else if (next_zM < next_z) {
  90. // Magnetic boundary comes before nuclear boundary, so increment magnetic.
  91. output[k][0] = std::max(next_zM - z, 0.0);
  92. output[k][1] = sigmaM[magnetic];
  93. next_zM += dM[++magnetic];
  94. } else {
  95. // Nuclear boundary comes before magnetic boundary
  96. // OR nuclear and magnetic boundaries match but interfaces are different.
  97. // so increment nuclear.
  98. output[k][0] = std::max(next_z - z, 0.0);
  99. output[k][1] = sigma[nuclear];
  100. next_z += d[++nuclear];
  101. }
  102. z += output[k][0];
  103. k++;
  104. }
  105. return k;
  106. }
  107. extern "C"
  108. int
  109. contract_by_step(int n, double d[], double sigma[],
  110. double rho[], double irho[], double dh)
  111. {
  112. double dz,rholeft,rhoarea,irholeft,irhoarea;
  113. int newi = 0;
  114. int i;
  115. dz = d[0];
  116. rholeft=rho[0];
  117. irholeft=irho[0];
  118. rhoarea = dz*rholeft;
  119. irhoarea = dz*irholeft;
  120. for (i=1; i < n; i++) {
  121. if (sigma[i] != 0.
  122. && fabs(rholeft-rho[i]) < dh
  123. && fabs(irholeft-irho[i]) < dh) {
  124. dz += d[i];
  125. rhoarea += d[i]*rho[i];
  126. irhoarea += d[i]*irho[i];
  127. } else {
  128. d[newi] = dz;
  129. if (newi > 0) {
  130. rho[newi] = rhoarea/dz;
  131. irho[newi] = irhoarea/dz;
  132. }
  133. newi++;
  134. dz = d[i];
  135. rholeft=rho[i];
  136. irholeft=irho[i];
  137. rhoarea = dz*rholeft;
  138. irhoarea = dz*irholeft;
  139. }
  140. }
  141. d[newi]=dz;
  142. rho[newi]=rho[n-1];
  143. irho[newi]=irho[n-1];
  144. return ++newi;
  145. }
  146. #ifdef GREEDY
  147. extern "C"
  148. int
  149. contract_by_area(int n, double d[], double sigma[],
  150. double rho[], double irho[], double dA)
  151. {
  152. double dz;
  153. double rholo, rhohi, rhoarea;
  154. double irholo, irhohi, irhoarea;
  155. int i, newi;
  156. i=newi=1; /* Skip the substrate */
  157. while (i < n) {
  158. /* Get ready for the next layer */
  159. /* Accumulation of the first row happens in the inner loop */
  160. dz = rhoarea = irhoarea = 0.;
  161. rholo=rhohi=rho[i];
  162. irholo=irhohi=irho[i];
  163. /* Accumulate slices into layer */
  164. for (;;) {
  165. assert(i < n);
  166. /* Accumulate next slice */
  167. dz += d[i];
  168. rhoarea+=d[i]*rho[i];
  169. irhoarea+=d[i]*irho[i];
  170. /* If no more slices or sigma != 0, break immediately */
  171. if (++i == n || sigma[i-1] != 0.) break;
  172. /* If next slice won't fit, break */
  173. if (rho[i] < rholo) rholo = rho[i];
  174. if (rho[i] > rhohi) rhohi = rho[i];
  175. if ((rhohi-rholo)*(dz+d[i]) > dA) break;
  176. if (irho[i] < irholo) irholo = irho[i];
  177. if (irho[i] > irhohi) irhohi = irho[i];
  178. if ((irhohi-irholo)*(dz+d[i]) > dA) break;
  179. }
  180. /* dz is only going to be zero if there is a forced break due to
  181. * sigma, or if we are accumulating a substrate. In either case,
  182. * we want to accumulate the zero length layer
  183. */
  184. /* if (dz == 0) continue; */
  185. /* Save the layer */
  186. assert(newi < n);
  187. d[newi] = dz;
  188. if (i == n) {
  189. /* printf("contract: adding final sld at %d\n",newi); */
  190. /* Last layer uses surface values */
  191. rho[newi] = rho[n-1];
  192. irho[newi] = irho[n-1];
  193. /* No interface for final layer */
  194. } else {
  195. /* Middle layers uses average values */
  196. rho[newi] = rhoarea / dz;
  197. irho[newi] = irhoarea / dz;
  198. sigma[newi] = sigma[i-1];
  199. } /* First layer uses substrate values */
  200. newi++;
  201. }
  202. return newi;
  203. }
  204. extern "C"
  205. int
  206. contract_mag(int n, double d[], double sigma[],
  207. double rho[], double irho[],
  208. double rhoM[], double thetaM[],
  209. double dA)
  210. {
  211. double dz, weighted_dz, weight;
  212. double rholo, rhohi, rhoarea;
  213. double irholo, irhohi, irhoarea;
  214. double maglo, maghi, mag, rhoMarea, thetaMarea;
  215. int i, newi;
  216. i=newi=1; /* Skip the substrate */
  217. while (i < n) {
  218. /* Get ready for the next layer */
  219. /* Accumulation of the first row happens in the inner loop */
  220. dz = weighted_dz = 0;
  221. rhoarea = irhoarea = rhoMarea = thetaMarea = 0.;
  222. rholo=rhohi=rho[i];
  223. irholo=irhohi=irho[i];
  224. maglo=maghi=rhoM[i]*cos(thetaM[i]*M_PI/180.);
  225. /* Accumulate slices into layer */
  226. for (;;) {
  227. assert(i < n);
  228. /* Accumulate next slice */
  229. dz += d[i];
  230. rhoarea+=d[i]*rho[i];
  231. irhoarea+=d[i]*irho[i];
  232. /* Weight the magnetic signal by the in-plane contribution
  233. * when accumulating rhoM and thetaM. */
  234. weight = cos(thetaM[i]*M_PI/180.);
  235. mag = rhoM[i]*weight;
  236. rhoMarea += d[i]*rhoM[i]*weight;
  237. thetaMarea += d[i]*thetaM[i]*weight;
  238. weighted_dz += d[i]*weight;
  239. /* If no more slices or sigma != 0, break immediately */
  240. if (++i == n || sigma[i-1] != 0.) break;
  241. /* If next slice exceeds limit then break */
  242. if (rho[i] < rholo) rholo = rho[i];
  243. if (rho[i] > rhohi) rhohi = rho[i];
  244. if ((rhohi-rholo)*(dz+d[i]) > dA) break;
  245. if (irho[i] < irholo) irholo = irho[i];
  246. if (irho[i] > irhohi) irhohi = irho[i];
  247. if ((irhohi-irholo)*(dz+d[i]) > dA) break;
  248. if (mag < maglo) maglo = mag;
  249. if (mag > maghi) maghi = mag;
  250. if ((maghi-maglo)*(dz+d[i]) > dA) break;
  251. }
  252. /* Save the layer */
  253. assert(newi < n);
  254. d[newi] = dz;
  255. if (i == n) {
  256. /* Last layer uses surface values */
  257. rho[newi] = rho[n-1];
  258. irho[newi] = irho[n-1];
  259. rhoM[newi] = rhoM[n-1];
  260. thetaM[newi] = thetaM[n-1];
  261. /* No interface for final layer */
  262. } else {
  263. /* Middle layers uses average values */
  264. rho[newi] = rhoarea / dz;
  265. irho[newi] = irhoarea / dz;
  266. rhoM[newi] = rhoMarea / weighted_dz;
  267. thetaM[newi] = thetaMarea / weighted_dz;
  268. sigma[newi] = sigma[i-1];
  269. } /* First layer uses substrate values */
  270. newi++;
  271. }
  272. return newi;
  273. }
  274. #else
  275. #error "dynamic programming solution not yet implemented"
  276. // A correct solution will have to incorporate forced breaks at sigma != 0,
  277. // with a strong preference to merge layers the closer you are to the
  278. // interface. The current solution does not do this.
  279. static void
  280. find_breaks(int n, double d[], double rho[], double irho[], double dA,
  281. double cost[], int nextbreak[])
  282. {
  283. const double breakpenalty=dA;
  284. int i;
  285. std::cout << "n=" << n << std::endl;
  286. cost[n]=0.;
  287. nextbreak[n]=-1;
  288. for (i=n-1; i >= 0; i--) {
  289. double dz,rhoarea,irhoarea,bestcost;
  290. int j, best;
  291. if (i%50==0) std::cout << "i=" << i << std::endl;
  292. dz=rhoarea=irhoarea=0.;
  293. bestcost = cost[i+1];
  294. best=i+1;
  295. for (j=i; j<n; j++) {
  296. double rhoj, irhoj;
  297. double rhocost, irhocost, costj;
  298. int k;
  299. // std::cout << "i=" << i << ", j=" << j << std::endl;
  300. /* Maintain running average */
  301. rhoarea += rho[j]*d[j];
  302. irhoarea += irho[j]*d[j];
  303. dz += d[j];
  304. if (j==n-1) { /* substrate layer: use substrate parameters */
  305. if (i==0) break; /* infinity penalty for only one layer */
  306. rhoj=rho[j]; irhoj=irho[j];
  307. } else if (i == 0) { /* vacuum layer: use vacuum parameters */
  308. rhoj=rho[0]; irhoj=irho[0];
  309. } else { /* intermediate layer: use average parameters */
  310. rhoj=rhoarea/dz; irhoj=irhoarea/dz;
  311. }
  312. /* simple penalty --- may need rms penalty if sharp spikes need
  313. * to be preserved */
  314. rhocost = irhocost = 0.;
  315. for (k=i; k <= j; k++) {
  316. // std::cout << "i=" << i << ", j=" << j << ", k=" << k << std::endl;
  317. rhocost += fabs(rho[k]-rhoj)/d[j];
  318. irhocost += fabs(irho[k]-irhoj)/d[j];
  319. }
  320. /* cost is monotonically increasing.
  321. * Proof:
  322. * Rho chosen as average of all current slices.
  323. * Any other rho increases cost of current slices.
  324. * If next slice changes rho, cost for current slices
  325. * slices increases. If next slice j doesn't change rho,
  326. * cost increases by |rho_j-rho|*d_j >= 0, so total cost
  327. * increases.
  328. * Since cost is increasing, limit our search to this
  329. * j if we have already exceeded the allowed cost of a
  330. * slice. This changes the algorithm from O(n^2) to O(km)
  331. * where k is the average cost per slice.
  332. */
  333. if (rhocost > dA || irhocost > dA) break;
  334. costj = rhocost + irhocost + cost[j+1];
  335. if (costj < bestcost) {
  336. best = j+1;
  337. bestcost = costj;
  338. }
  339. }
  340. cost[i] = bestcost + breakpenalty;
  341. nextbreak[i] = best;
  342. }
  343. }
  344. int merge_breaks(double d[], double rho[], double irho[], int nextbreak[])
  345. {
  346. int i, next, newi;
  347. newi = 0;
  348. i = 0;
  349. next = nextbreak[0];
  350. while (next > 0) {
  351. double dz,rhoarea,irhoarea,bestcost;
  352. int j, best;
  353. /* Compute average */
  354. dz=rhoarea=irhoarea=0.;
  355. while (i < next) {
  356. rhoarea += rho[i]*d[i];
  357. irhoarea += irho[i]*d[i];
  358. dz += d[i];
  359. i++;
  360. }
  361. next = nextbreak[i];
  362. d[newi]=dz;
  363. if (next < 0) { /* substrate layer: use substrate parameters */
  364. rho[newi]=rho[i-1];
  365. irho[newi]=irho[i-1];
  366. } else if (newi==0) { /* vacuum layer: use vacuum parameters */
  367. /* NOP since newi==0: rho[newi] = rho[0]; */
  368. /* NOP since newi==0: irho[newi] = irho[0]; */
  369. } else { /* intermediate layer: use average parameters */
  370. rho[newi] = rhoarea/dz;
  371. irho[newi] = irhoarea/dz;
  372. }
  373. newi++;
  374. }
  375. return newi;
  376. }
  377. extern "C"
  378. int
  379. contract_by_area(int n, double d[], double sigma[],
  380. double rho[], double irho[], double dA,
  381. void *work)
  382. {
  383. // Note: currently ignores sigma!
  384. find_breaks(n, d, rho, irho, dA, cost, breaks);
  385. int new_n = merge_breaks(d, rho, irho, breaks);
  386. return new_n;
  387. }
  388. #endif
  389. // $Id: contract_profile.cc 2 2005-08-02 00:19:11Z pkienzle $