1 /*
2 Teem: Tools to process and visualize scientific data and images .
3 Copyright (C) 2012, 2011, 2010, 2009 University of Chicago
4 Copyright (C) 2008, 2007, 2006, 2005 Gordon Kindlmann
5 Copyright (C) 2004, 2003, 2002, 2001, 2000, 1999, 1998 University of Utah
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Lesser General Public License
9 (LGPL) as published by the Free Software Foundation; either
10 version 2.1 of the License, or (at your option) any later version.
11 The terms of redistributing and/or modifying this software also
12 include exceptions to the LGPL that facilitate static linking.
13
14 This library is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
18
19 You should have received a copy of the GNU Lesser General Public License
20 along with this library; if not, write to Free Software Foundation, Inc.,
21 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 */
23
24
25 #include "pull.h"
26 #include "privatePull.h"
27
28 /*
29 ** issues:
30 ** does everything work on the first iteration
31 ** how to handle the needed extra probe for d strength / d scale
32 ** how are force/energy along scale handled differently than in space?
33 */
34
35 static double
_pointDistSqrd(pullContext * pctx,pullPoint * AA,pullPoint * BB)36 _pointDistSqrd(pullContext *pctx, pullPoint *AA, pullPoint *BB) {
37 double diff[4];
38 ELL_4V_SUB(diff, AA->pos, BB->pos);
39 ELL_3V_SCALE(diff, 1/pctx->sysParm.radiusSpace, diff);
40 diff[3] /= pctx->sysParm.radiusScale;
41 return ELL_4V_DOT(diff, diff);
42 }
43
44 /*
45 ** this sets, in task->neighPoint (*NOT* point->neighPoint), all the
46 ** points in neighboring bins with which we might possibly interact,
47 ** and returns the number of such points.
48 */
49 static unsigned int
_neighBinPoints(pullTask * task,pullBin * bin,pullPoint * point,double distTest)50 _neighBinPoints(pullTask *task, pullBin *bin, pullPoint *point,
51 double distTest) {
52 static const char me[]="_neighBinPoints";
53 unsigned int nn, herPointIdx, herBinIdx;
54 pullBin *herBin;
55 pullPoint *herPoint;
56
57 nn = 0;
58 herBinIdx = 0;
59 while ((herBin = bin->neighBin[herBinIdx])) {
60 for (herPointIdx=0; herPointIdx<herBin->pointNum; herPointIdx++) {
61 herPoint = herBin->point[herPointIdx];
62 /*
63 printf("!%s(%u): neighbin %u has point %u\n", me,
64 point->idtag, herBinIdx, herPoint->idtag);
65 */
66 /* can't interact with myself, or anything nixed */
67 if (point != herPoint
68 && !(herPoint->status & PULL_STATUS_NIXME_BIT)) {
69 if (distTest
70 && _pointDistSqrd(task->pctx, point, herPoint) > distTest) {
71 continue;
72 }
73 if (nn+1 < _PULL_NEIGH_MAXNUM) {
74 task->neighPoint[nn++] = herPoint;
75 /*
76 printf("%s(%u): neighPoint[%u] = %u\n",
77 me, point->idtag, nn-1, herPoint->idtag);
78 */
79 } else {
80 fprintf(stderr, "%s: hit max# (%u) poss. neighbors (from bins)\n",
81 me, _PULL_NEIGH_MAXNUM);
82 }
83 }
84 }
85 herBinIdx++;
86 }
87 /* also have to consider things in the add queue */
88 for (herPointIdx=0; herPointIdx<task->addPointNum; herPointIdx++) {
89 herPoint = task->addPoint[herPointIdx];
90 if (point != herPoint) {
91 if (distTest
92 && _pointDistSqrd(task->pctx, point, herPoint) > distTest) {
93 continue;
94 }
95 if (nn+1 < _PULL_NEIGH_MAXNUM) {
96 task->neighPoint[nn++] = herPoint;
97 } else {
98 fprintf(stderr, "%s: hit max# (%u) poss neighs (add queue len %u)\n",
99 me, _PULL_NEIGH_MAXNUM, task->addPointNum);
100 }
101 }
102 }
103 return nn;
104 }
105
106 /*
107 ** compute the energy at "me" due to "she", and
108 ** the gradient vector of her energy (probably pointing towards her)
109 **
110 ** we're passed spaceDist to save us work of recomputing sqrt()
111 **
112 ** egrad will be NULL if this is being called only to assess
113 ** the energy at this point, rather than for learning how to move it
114 */
115 double
_pullEnergyInterParticle(pullContext * pctx,pullPoint * me,const pullPoint * she,double spaceDist,double scaleDist,double egrad[4])116 _pullEnergyInterParticle(pullContext *pctx, pullPoint *me,
117 const pullPoint *she,
118 double spaceDist, double scaleDist,
119 /* output */
120 double egrad[4]) {
121 char meme[]="pullEnergyInterParticle";
122 double diff[4], spaceRad, scaleRad, rr, ss, uu, beta,
123 en, den, enR, denR, enS, denS, enWR, enWS, denWR, denWS,
124 *parmR, *parmS, *parmW,
125 (*evalR)(double *, double, const double parm[PULL_ENERGY_PARM_NUM]),
126 (*evalS)(double *, double, const double parm[PULL_ENERGY_PARM_NUM]),
127 (*evalW)(double *, double, const double parm[PULL_ENERGY_PARM_NUM]);
128 int scaleSgn;
129
130 /* the vector "diff" goes from her, to me, in both space and scale */
131 ELL_4V_SUB(diff, me->pos, she->pos);
132 /* computed by caller: spaceDist = ELL_3V_LEN(diff); */
133 /* computed by caller: scaleDist = AIR_ABS(diff[3]); */
134 spaceRad = pctx->sysParm.radiusSpace;
135 scaleRad = pctx->sysParm.radiusScale;
136 rr = spaceDist/spaceRad;
137 if (pctx->haveScale) {
138 ss = scaleDist/scaleRad;
139 scaleSgn = airSgn(diff[3]);
140 } else {
141 ss = 0;
142 scaleSgn = 1;
143 }
144 if (rr > 1 || ss > 1) {
145 if (egrad) {
146 ELL_4V_SET(egrad, 0, 0, 0, 0);
147 }
148 return 0;
149 }
150 if (rr == 0 && ss == 0) {
151 fprintf(stderr, "%s: pos(%u) == pos(%u) !! (%g,%g,%g,%g)\n",
152 meme, me->idtag, she->idtag,
153 me->pos[0], me->pos[1], me->pos[2], me->pos[3]);
154 if (egrad) {
155 ELL_4V_SET(egrad, 0, 0, 0, 0);
156 }
157 return 0;
158 }
159 #if PULL_HINTER
160 if (pullProcessModeDescent == pctx->task[0]->processMode
161 && pctx->nhinter && pctx->nhinter->data) {
162 unsigned int ri, si, sz;
163 float *hint;
164 hint = AIR_CAST(float *, pctx->nhinter->data);
165 sz = pctx->nhinter->axis[0].size;
166 ri = airIndex(-1.0, rr, 1.0, sz);
167 si = airIndex(-1.0, ss*scaleSgn, 1.0, sz);
168 hint[ri + sz*si] += 1;
169 }
170 #endif
171
172 parmR = pctx->energySpecR->parm;
173 evalR = pctx->energySpecR->energy->eval;
174 parmS = pctx->energySpecS->parm;
175 evalS = pctx->energySpecS->energy->eval;
176 switch (pctx->interType) {
177 case pullInterTypeJustR:
178 /* _pullVolumeSetup makes sure that
179 !pctx->haveScale iff pullInterTypeJustR == pctx->interType */
180 en = evalR(&denR, rr, parmR);
181 if (egrad) {
182 denR *= 1.0/(spaceRad*spaceDist);
183 ELL_3V_SCALE(egrad, denR, diff);
184 egrad[3] = 0;
185 }
186 break;
187 case pullInterTypeUnivariate:
188 uu = sqrt(rr*rr + ss*ss);
189 en = evalR(&den, uu, parmR);
190 if (egrad) {
191 ELL_3V_SCALE(egrad, den/(uu*spaceRad*spaceRad), diff);
192 egrad[3] = den*diff[3]/(uu*scaleRad*scaleRad);
193 }
194 break;
195 case pullInterTypeSeparable:
196 enR = evalR(&denR, rr, parmR);
197 enS = evalS(&denS, ss, parmS);
198 en = enR*enS;
199 if (egrad) {
200 ELL_3V_SCALE(egrad, denR*enS/(spaceRad*spaceDist), diff);
201 egrad[3] = enR*airSgn(diff[3])*denS/scaleRad;
202 }
203 break;
204 case pullInterTypeAdditive:
205 parmW = pctx->energySpecWin->parm;
206 evalW = pctx->energySpecWin->energy->eval;
207 enR = evalR(&denR, rr, parmR);
208 enS = evalS(&denS, ss, parmS);
209 enWR = evalW(&denWR, rr, parmW);
210 enWS = evalW(&denWS, ss, parmW);
211 beta = pctx->sysParm.beta;
212 en = AIR_LERP(beta, enR*enWS, enS*enWR);
213 if (egrad) {
214 double egradR[4], egradS[4];
215 ELL_3V_SCALE(egradR, denR*enWS/(spaceRad*spaceDist), diff);
216 ELL_3V_SCALE(egradS, denWR*enS/(spaceRad*spaceDist), diff);
217 egradR[3] = enR*scaleSgn*denWS/scaleRad;
218 egradS[3] = enWR*scaleSgn*denS/scaleRad;
219 ELL_4V_LERP(egrad, beta, egradR, egradS);
220 }
221 break;
222 default:
223 fprintf(stderr, "!%s: sorry, intertype %d unimplemented", meme,
224 pctx->interType);
225 en = AIR_NAN;
226 if (egrad) {
227 ELL_4V_SET(egrad, AIR_NAN, AIR_NAN, AIR_NAN, AIR_NAN);
228 }
229 break;
230 }
231 /*
232 printf("%s: %u <-- %u = %g,%g,%g -> egrad = %g,%g,%g, enr = %g\n",
233 meme, me->idtag, she->idtag,
234 diff[0], diff[1], diff[2],
235 egrad[0], egrad[1], egrad[2], enr);
236 */
237 return en;
238 }
239
240 int
pullEnergyPlot(pullContext * pctx,Nrrd * nplot,double xx,double yy,double zz,unsigned int res)241 pullEnergyPlot(pullContext *pctx, Nrrd *nplot,
242 double xx, double yy, double zz,
243 unsigned int res) {
244 static const char meme[]="pullEnergyPlot";
245 pullPoint *me, *she;
246 airArray *mop;
247 double dir[3], len, *plot, _rr, _ss, rr, ss, enr, egrad[4];
248 size_t size[3];
249 unsigned int ri, si;
250
251 if (!( pctx && nplot )) {
252 biffAddf(PULL, "%s: got NULL pointer", meme);
253 return 1;
254 }
255 ELL_3V_SET(dir, xx, yy, zz);
256 if (!ELL_3V_LEN(dir)) {
257 biffAddf(PULL, "%s: need non-zero length dir", meme);
258 return 1;
259 }
260 ELL_3V_NORM(dir, dir, len);
261 ELL_3V_SET(size, 3, res, res);
262 if (nrrdMaybeAlloc_nva(nplot, nrrdTypeDouble, 3, size)) {
263 biffMovef(PULL, NRRD, "%s: trouble allocating output", meme);
264 return 1;
265 }
266
267 mop = airMopNew();
268 me = pullPointNew(pctx);
269 she = pullPointNew(pctx);
270 airMopAdd(mop, me, (airMopper)pullPointNix, airMopAlways);
271 airMopAdd(mop, she, (airMopper)pullPointNix, airMopAlways);
272 ELL_4V_SET(me->pos, 0, 0, 0, 0);
273 plot = AIR_CAST(double *, nplot->data);
274 for (si=0; si<res; si++) {
275 _ss = AIR_AFFINE(0, si, res-1, -1.0, 1.0);
276 ss = _ss*pctx->sysParm.radiusScale;
277 for (ri=0; ri<res; ri++) {
278 _rr = AIR_AFFINE(0, ri, res-1, -1.0, 1.0);
279 rr = _rr*pctx->sysParm.radiusSpace;
280 ELL_3V_SCALE(she->pos, rr, dir);
281 she->pos[3] = ss;
282 enr = _pullEnergyInterParticle(pctx, me, she,
283 AIR_ABS(rr), AIR_ABS(ss), egrad);
284 plot[0] = enr;
285 plot[1] = ELL_3V_DOT(egrad, dir);
286 plot[2] = egrad[3];
287 plot += 3;
288 }
289 }
290
291 airMopOkay(mop);
292 return 0;
293 }
294
295 /*
296 ** computes energy from neighboring points. The non-NULLity of
297 ** "egradSum" determines the energy *gradient* is computed (with
298 ** possible constraint modifications) and stored there
299 **
300 ** always computed:
301 ** point->neighInterNum
302 ** point->neighDistMean
303 **
304 ** if pullProcessModeNeighLearn == task->processMode:
305 ** point->neighCovar
306 ** point->neighTanCovar
307 ** point->neighNum
308 **
309 ** 0=0 1=1 2=2 3=3
310 ** (4) 4=5 5=6 6=7
311 ** (8) (9) 7=10 8=11
312 ** (12) (13) (14) 9=15
313 */
314 double
_pullEnergyFromPoints(pullTask * task,pullBin * bin,pullPoint * point,double egradSum[4])315 _pullEnergyFromPoints(pullTask *task, pullBin *bin, pullPoint *point,
316 /* output */
317 double egradSum[4]) {
318 static const char me[]="_pullEnergyFromPoints";
319 double energySum, spaDistSqMax; /* modeWghtSum */
320 int nlist, /* we enable the re-use of neighbor lists between inters, or,
321 at system start, creation of neighbor lists */
322 ntrue; /* we search all possible neighbors availble in the bins
323 (either because !nlist, or, this iter we learn true
324 subset of interacting neighbors). This could also
325 be called "dontreuse" or something like that */
326 unsigned int nidx,
327 nnum; /* how much of task->neigh[] we use */
328
329 /* set nlist and ntrue */
330 if (pullProcessModeNeighLearn == task->processMode) {
331 /* we're here to both learn and store the true interacting neighbors */
332 nlist = AIR_TRUE;
333 ntrue = AIR_TRUE;
334 } else if (pullProcessModeAdding == task->processMode
335 || pullProcessModeNixing == task->processMode) {
336 /* we assume that the work of learning neighbors has already been
337 done, so we can reuse them now */
338 nlist = AIR_TRUE;
339 ntrue = AIR_FALSE;
340 } else if (pullProcessModeDescent == task->processMode) {
341 if (task->pctx->sysParm.neighborTrueProb < 1) {
342 nlist = AIR_TRUE;
343 if (egradSum) {
344 /* We allow the neighbor list optimization only when we're also
345 asked to compute the energy gradient, since that's the first
346 part of moving the particle. */
347 ntrue = (0 == task->pctx->iter
348 || (airDrandMT_r(task->rng)
349 < task->pctx->sysParm.neighborTrueProb));
350 } else {
351 /* When we're not getting the energy gradient, we're being
352 called to test the waters at possible new locations, in which
353 case we can't be changing the effective neighborhood */
354 ntrue = AIR_TRUE;
355 }
356 } else {
357 /* never trying neighborhood caching */
358 nlist = AIR_FALSE;
359 ntrue = AIR_TRUE;
360 }
361 } else {
362 fprintf(stderr, "%s: process mode %d unrecognized!\n", me,
363 task->processMode);
364 return AIR_NAN;
365 }
366
367 /* NOTE: can't have both nlist and ntrue false. possibilities are:
368 **
369 ** nlist:
370 ** true false
371 ** true X X
372 ** ntrue:
373 ** false X
374 */
375 /*
376 printf("!%s(%u), nlist = %d, ntrue = %d\n", me, point->idtag,
377 nlist, ntrue);
378 */
379 /* set nnum and task->neigh[] */
380 if (ntrue) {
381 /* this finds the over-inclusive set of all possible interacting
382 points, based on bin membership as well the task's add queue */
383 nnum = _neighBinPoints(task, bin, point, 1.0);
384 if (nlist) {
385 airArrayLenSet(point->neighPointArr, 0);
386 }
387 } else {
388 /* (nlist true) this iter we re-use this point's existing neighbor
389 list, copying it into the the task's neighbor list to simulate
390 the action of _neighBinPoints() */
391 nnum = point->neighPointNum;
392 for (nidx=0; nidx<nnum; nidx++) {
393 task->neighPoint[nidx] = point->neighPoint[nidx];
394 }
395 }
396
397 /* loop through neighbor points */
398 spaDistSqMax = (task->pctx->sysParm.radiusSpace
399 * task->pctx->sysParm.radiusSpace);
400 /*
401 printf("%s: radiusSpace = %g -> spaDistSqMax = %g\n", me,
402 task->pctx->sysParm.radiusSpace, spaDistSqMax);
403 */
404 /* modeWghtSum = 0; */
405 energySum = 0;
406 point->neighInterNum = 0;
407 point->neighDistMean = 0.0;
408 if (pullProcessModeNeighLearn == task->processMode) {
409 ELL_10V_ZERO_SET(point->neighCovar);
410 point->stability = 0.0;
411 #if PULL_TANCOVAR
412 if (task->pctx->ispec[pullInfoTangent1]) {
413 double *tng;
414 float outer[9];
415 tng = point->info + task->pctx->infoIdx[pullInfoTangent1];
416 ELL_3MV_OUTER_TT(outer, float, tng, tng);
417 point->neighTanCovar[0] = outer[0];
418 point->neighTanCovar[1] = outer[1];
419 point->neighTanCovar[2] = outer[2];
420 point->neighTanCovar[3] = outer[4];
421 point->neighTanCovar[4] = outer[5];
422 point->neighTanCovar[5] = outer[8];
423 }
424 #endif
425 }
426 if (egradSum) {
427 ELL_4V_SET(egradSum, 0, 0, 0, 0);
428 }
429 for (nidx=0; nidx<nnum; nidx++) {
430 double diff[4], spaDistSq, spaDist, sclDist, enr, egrad[4];
431 pullPoint *herPoint;
432
433 herPoint = task->neighPoint[nidx];
434 if (herPoint->status & PULL_STATUS_NIXME_BIT) {
435 /* this point is not long for this world, pass over it */
436 continue;
437 }
438 ELL_4V_SUB(diff, point->pos, herPoint->pos); /* me - her */
439 spaDistSq = ELL_3V_DOT(diff, diff);
440 /*
441 printf("!%s: %u:%g,%g,%g <-- %u:%g,%g,%g = sqd %g %s %g\n", me,
442 point->idtag, point->pos[0], point->pos[1], point->pos[2],
443 herPoint->idtag,
444 herPoint->pos[0], herPoint->pos[1], herPoint->pos[2],
445 spaDistSq, spaDistSq > spaDistSqMax ? ">" : "<=", spaDistSqMax);
446 */
447 if (spaDistSq > spaDistSqMax) {
448 continue;
449 }
450 sclDist = AIR_ABS(diff[3]);
451 if (sclDist > task->pctx->sysParm.radiusScale) {
452 continue;
453 }
454 spaDist = sqrt(spaDistSq);
455 /* we pass spaDist to avoid recomputing sqrt(), and sclDist for
456 stupid consistency */
457 enr = _pullEnergyInterParticle(task->pctx, point, herPoint,
458 spaDist, sclDist,
459 egradSum ? egrad : NULL);
460 #if 0
461 /* sanity checking on energy derivatives */
462 if (enr && egradSum) {
463 double _pos[4], tdf[4], ee[2], eps=0.000001, apegrad[4], quot[4];
464 unsigned int cord, pan;
465 ELL_4V_COPY(_pos, point->pos);
466
467 for (cord=0; cord<=3; cord++) {
468 for (pan=0; pan<=1; pan++) {
469 point->pos[cord] = _pos[cord] + (!pan ? -1 : +1)*eps;
470 ELL_4V_SUB(tdf, point->pos, herPoint->pos);
471 ee[pan] = _pullEnergyInterParticle(task->pctx, point, herPoint,
472 ELL_3V_LEN(tdf), AIR_ABS(tdf[3]), NULL);
473 }
474 point->pos[cord] = _pos[cord];
475 apegrad[cord] = (ee[1] - ee[0])/(2*eps);
476 quot[cord] = apegrad[cord]/egrad[cord];
477 }
478 if ( AIR_ABS(1.0 - quot[0]) > 0.01 ||
479 AIR_ABS(1.0 - quot[1]) > 0.01 ||
480 AIR_ABS(1.0 - quot[2]) > 0.01 ||
481 (task->pctx->haveScale && AIR_ABS(1.0 - quot[3]) > 0.01) ) {
482 printf("!%s(%u<-%u): ---------- claim egrad (%g,%g,%g,%g)\n", me,
483 point->idtag, herPoint->idtag, egrad[0], egrad[1], egrad[2], egrad[3]);
484 printf("!%s(%u<-%u): measr egrad (%g,%g,%g,%g)\n", me,
485 point->idtag, herPoint->idtag, apegrad[0], apegrad[1], apegrad[2], apegrad[3]);
486 printf("!%s(%u<-%u): quot (%g,%g,%g,%g)\n", me,
487 point->idtag, herPoint->idtag, quot[0], quot[1], quot[2], quot[3]);
488 }
489
490 ELL_4V_COPY(point->pos, _pos);
491 }
492 #endif
493
494 if (enr) {
495 /* there is some non-zero energy due to her; and we assume that
496 its not just a fluke zero-crossing of the potential profile */
497 double ndist;
498
499 point->neighInterNum++;
500 if (nlist && ntrue) {
501 unsigned int ii;
502 /* we have to record that we had an interaction with this point */
503 ii = airArrayLenIncr(point->neighPointArr, 1);
504 point->neighPoint[ii] = herPoint;
505 }
506 energySum += enr;
507 ELL_3V_SCALE(diff, 1.0/task->pctx->sysParm.radiusSpace, diff);
508 if (task->pctx->haveScale) {
509 diff[3] /= task->pctx->sysParm.radiusScale;
510 }
511 ndist = ELL_4V_LEN(diff);
512 point->neighDistMean += ndist;
513 if (pullProcessModeNeighLearn == task->processMode) {
514 float outer[16];
515 ELL_4MV_OUTER_TT(outer, float, diff, diff);
516 point->neighCovar[0] += outer[0];
517 point->neighCovar[1] += outer[1];
518 point->neighCovar[2] += outer[2];
519 point->neighCovar[3] += outer[3];
520 point->neighCovar[4] += outer[5];
521 point->neighCovar[5] += outer[6];
522 point->neighCovar[6] += outer[7];
523 point->neighCovar[7] += outer[10];
524 point->neighCovar[8] += outer[11];
525 point->neighCovar[9] += outer[15];
526 #if PULL_TANCOVAR
527 if (task->pctx->ispec[pullInfoTangent1]) {
528 double *tng;
529 tng = herPoint->info + task->pctx->infoIdx[pullInfoTangent1];
530 ELL_3MV_OUTER_TT(outer, float, tng, tng);
531 point->neighTanCovar[0] += outer[0];
532 point->neighTanCovar[1] += outer[1];
533 point->neighTanCovar[2] += outer[2];
534 point->neighTanCovar[3] += outer[4];
535 point->neighTanCovar[4] += outer[5];
536 point->neighTanCovar[5] += outer[8];
537 }
538 #endif
539 }
540 if (egradSum) {
541 ELL_4V_INCR(egradSum, egrad);
542 }
543 }
544 }
545
546 #define CNORM(M) \
547 sqrt(M[0]*M[0] + 2*M[1]*M[1] + 2*M[2]*M[2] + 2*M[3]*M[3] \
548 + M[4]*M[4] + 2*M[5]*M[5] + 2*M[6]*M[6] \
549 + M[7]*M[7] + 2*M[8]*M[8] \
550 + M[9]*M[9])
551 #define CTRACE(M) (M[0] + M[4] + M[7] + M[9])
552
553 /* finish computing things averaged over neighbors */
554 if (point->neighInterNum) {
555 point->neighDistMean /= point->neighInterNum;
556 if (pullProcessModeNeighLearn == task->processMode) {
557 double Css, trc;
558 ELL_10V_SCALE(point->neighCovar, 1.0f/point->neighInterNum,
559 point->neighCovar);
560 Css = point->neighCovar[9];
561 trc = CTRACE(point->neighCovar);
562 point->stability = AIR_CAST(float, (trc
563 ? (task->pctx->targetDim * Css)/trc
564 : 0.0));
565 #if PULL_TANCOVAR
566 /* using 1 + # neigh because this includes tan1 of point itself */
567 ELL_6V_SCALE(point->neighTanCovar, 1.0f/(1 + point->neighInterNum),
568 point->neighTanCovar);
569 #endif
570 }
571 } else {
572 /* we had no neighbors at all */
573 point->neighDistMean = 0.0; /* shouldn't happen in any normal case */
574 /* point->neighCovar,neighTanCovar stay as initialized above */
575 }
576 return energySum;
577 }
578
579 static double
_energyFromImage(pullTask * task,pullPoint * point,double egradSum[4])580 _energyFromImage(pullTask *task, pullPoint *point,
581 /* output */
582 double egradSum[4]) {
583 static const char me[]="_energyFromImage";
584 double energy, grad3[3], gamma;
585 int probed;
586
587 /* not sure I have the logic for this right
588 int ptrue;
589 if (task->pctx->probeProb < 1 && allowProbeProb) {
590 if (egrad) {
591 ptrue = (0 == task->pctx->iter
592 || airDrandMT_r(task->rng) < task->pctx->probeProb);
593 } else {
594 ptrue = AIR_FALSE;
595 }
596 } else {
597 ptrue = AIR_TRUE;
598 }
599 */
600 probed = AIR_FALSE;
601
602 #define MAYBEPROBE \
603 if (!probed) { \
604 if (pullProbe(task, point)) { \
605 fprintf(stderr, "%s: problem probing!!!\n%s\n", me, biffGetDone(PULL)); \
606 } \
607 probed = AIR_TRUE; \
608 }
609
610 gamma = task->pctx->sysParm.gamma;
611 energy = 0;
612 if (egradSum) {
613 ELL_4V_SET(egradSum, 0, 0, 0, 0);
614 }
615 if (task->pctx->flag.energyFromStrength
616 && task->pctx->ispec[pullInfoStrength]) {
617 double deltaScale, str0, str1, scl0, scl1, enr;
618 int sign;
619 if (!egradSum) {
620 /* just need the strength */
621 MAYBEPROBE;
622 enr = pullPointScalar(task->pctx, point, pullInfoStrength,
623 NULL, NULL);
624 energy += -gamma*enr;
625 } else {
626 /* need strength and its gradient */
627 /* randomize choice between forward and backward difference */
628 /* NOTE: since you only need one bit of random, you could re-used
629 a random int and look through its bits to determine forw vs
630 back differences, but this is probably not the bottleneck */
631 sign = 2*AIR_CAST(int, airRandInt_r(task->rng, 2)) - 1;
632 deltaScale = task->pctx->bboxMax[3] - task->pctx->bboxMin[3];
633 deltaScale *= sign*_PULL_STRENGTH_ENERGY_DELTA_SCALE;
634 scl1 = (point->pos[3] += deltaScale);
635 pullProbe(task, point);
636 str1 = pullPointScalar(task->pctx, point, pullInfoStrength,
637 NULL, NULL);
638 scl0 = (point->pos[3] -= deltaScale);
639 MAYBEPROBE;
640 str0 = pullPointScalar(task->pctx, point, pullInfoStrength,
641 NULL, NULL);
642 energy += -gamma*str0;
643 egradSum[3] += -gamma*(str1 - str0)/(scl1 - scl0);
644 /*
645 if (1560 < task->pctx->iter && 2350 == point->idtag) {
646 printf("%s(%u): egrad[3] = %g*((%g-%g)/(%g-%g) = %g/%g = %g)"
647 " = %g -> %g\n",
648 me, point->idtag, task->pctx->sysParm.gamma,
649 str1, str0, scl1, scl0,
650 str1 - str0, scl1 - scl0,
651 (str1 - str0)/(scl1 - scl0),
652 task->pctx->sysParm.gamma*(str1 - str0)/(scl1 - scl0),
653 egradSum[3]);
654 }
655 */
656 }
657 }
658 /* Note that height doesn't contribute to the energy if there is
659 a constraint associated with it */
660 if (task->pctx->ispec[pullInfoHeight]
661 && !task->pctx->ispec[pullInfoHeight]->constraint
662 && !(task->pctx->ispec[pullInfoHeightLaplacian]
663 && task->pctx->ispec[pullInfoHeightLaplacian]->constraint)) {
664 MAYBEPROBE;
665 energy += pullPointScalar(task->pctx, point, pullInfoHeight,
666 grad3, NULL);
667 if (egradSum) {
668 ELL_3V_INCR(egradSum, grad3);
669 }
670 }
671 if (task->pctx->ispec[pullInfoIsovalue]
672 && !task->pctx->ispec[pullInfoIsovalue]->constraint) {
673 /* we're only going towards an isosurface, not constrained to it
674 ==> set up a parabolic potential well around the isosurface */
675 double val;
676 MAYBEPROBE;
677 val = pullPointScalar(task->pctx, point, pullInfoIsovalue,
678 grad3, NULL);
679 energy += val*val;
680 if (egradSum) {
681 ELL_3V_SCALE_INCR(egradSum, 2*val, grad3);
682 }
683 }
684 return energy;
685 }
686 #undef MAYBEPROBE
687
688 /*
689 ** NOTE that the "egrad" being non-NULL has consequences for what gets
690 ** computed in _energyFromImage and _pullEnergyFromPoints:
691 **
692 ** NULL "egrad": we're simply learning the energy (and want to know it
693 ** as truthfully as possible) for the sake of inspecting system state
694 **
695 ** non-NULL "egrad": we're learning the current energy, but the real point
696 ** is to determine how to move the point to lower energy
697 **
698 ** the ignoreImage flag is a hack, to allow _pullPointProcessAdding to
699 ** do descent on a new point according to other points, but not the
700 ** image.
701 */
702 double
_pullPointEnergyTotal(pullTask * task,pullBin * bin,pullPoint * point,int ignoreImage,double egrad[4])703 _pullPointEnergyTotal(pullTask *task, pullBin *bin, pullPoint *point,
704 int ignoreImage,
705 /* output */
706 double egrad[4]) {
707 static const char me[]="_pullPointEnergyTotal";
708 double enrIm, enrPt, egradIm[4], egradPt[4], energy;
709
710 ELL_4V_SET(egradIm, 0, 0, 0, 0);
711 ELL_4V_SET(egradPt, 0, 0, 0, 0);
712 if (!( ignoreImage || 1.0 == task->pctx->sysParm.alpha )) {
713 enrIm = _energyFromImage(task, point, egrad ? egradIm : NULL);
714 task->pctx->count[pullCountEnergyFromImage] += 1;
715 if (egrad) {
716 task->pctx->count[pullCountForceFromImage] += 1;
717 }
718 } else {
719 enrIm = 0;
720 }
721 if (task->pctx->sysParm.alpha > 0.0) {
722 enrPt = _pullEnergyFromPoints(task, bin, point, egrad ? egradPt : NULL);
723 task->pctx->count[pullCountEnergyFromPoints] += 1;
724 if (egrad) {
725 task->pctx->count[pullCountForceFromPoints] += 1;
726 }
727 } else {
728 enrPt = 0;
729 }
730 energy = AIR_LERP(task->pctx->sysParm.alpha, enrIm, enrPt);
731 /*
732 printf("!%s(%u): energy = lerp(%g, im %g, pt %g) = %g\n", me,
733 point->idtag, task->pctx->sysParm.alpha, enrIm, enrPt, energy);
734 */
735 if (egrad) {
736 ELL_4V_LERP(egrad, task->pctx->sysParm.alpha, egradIm, egradPt);
737 /*
738 printf("!%s(%u): egradIm = %g %g %g %g\n", me, point->idtag,
739 egradIm[0], egradIm[1], egradIm[2], egradIm[3]);
740 printf("!%s(%u): egradPt = %g %g %g %g\n", me, point->idtag,
741 egradPt[0], egradPt[1], egradPt[2], egradPt[3]);
742 printf("!%s(%u): ---> force = %g %g %g %g\n", me,
743 point->idtag, force[0], force[1], force[2], force[3]);
744 */
745 }
746 if (task->pctx->sysParm.wall) {
747 unsigned int axi;
748 double dwe; /* derivative of wall energy */
749 for (axi=0; axi<4; axi++) {
750 dwe = point->pos[axi] - task->pctx->bboxMin[axi];
751 if (dwe > 0) {
752 /* pos not below min */
753 dwe = point->pos[axi] - task->pctx->bboxMax[axi];
754 if (dwe < 0) {
755 /* pos not above max */
756 dwe = 0;
757 }
758 }
759 energy += task->pctx->sysParm.wall*dwe*dwe/2;
760 if (egrad) {
761 egrad[axi] += task->pctx->sysParm.wall*dwe;
762 }
763 }
764 }
765 if (!AIR_EXISTS(energy)) {
766 fprintf(stderr, "!%s(%u): oops- non-exist energy %g\n", me, point->idtag,
767 energy);
768 }
769 return energy;
770 }
771
772 /*
773 ** distance limit on a particles motion in both r and s,
774 ** in rs-normalized space (sqrt((r/radiusSpace)^2 + (s/radiusScale)^2))
775 **
776 ** This means that if particles are jammed together in space,
777 ** they aren't allowed to move very far in scale, either, which
778 ** is a little weird, but probably okay.
779 */
780 double
_pullDistLimit(pullTask * task,pullPoint * point)781 _pullDistLimit(pullTask *task, pullPoint *point) {
782 double ret;
783
784 if (point->neighDistMean == 0 /* no known neighbors from last iter */
785 || pullEnergyZero == task->pctx->energySpecR->energy) {
786 ret = 1;
787 } else {
788 ret = _PULL_DIST_CAP_RSNORM*point->neighDistMean;
789 }
790 /* HEY: maybe task->pctx->voxelSizeSpace or voxelSizeScale should
791 be considered here? */
792 return ret;
793 }
794
795 /*
796 ** here is where the energy gradient is converted into force
797 */
798 int
_pullPointProcessDescent(pullTask * task,pullBin * bin,pullPoint * point,int ignoreImage)799 _pullPointProcessDescent(pullTask *task, pullBin *bin, pullPoint *point,
800 int ignoreImage) {
801 static const char me[]="_pullPointProcessDescent";
802 double energyOld, energyNew, egrad[4], force[4], posOld[4];
803 int stepBad, giveUp, hailMary;
804
805 task->pctx->count[pullCountDescent] += 1;
806 if (!point->stepEnergy) {
807 fprintf(stderr, "\n\n\n%s: whoa, point %u step is zero!!\n\n\n\n",
808 me, point->idtag);
809 /* HEY: need to track down how this can originate! */
810 /*
811 biffAddf(PULL, "%s: point %u step is zero!", me, point->idtag);
812 return 1;
813 */
814 point->stepEnergy = task->pctx->sysParm.stepInitial/100;
815 }
816
817 /* learn the energy at old location, and the energy gradient */
818 energyOld = _pullPointEnergyTotal(task, bin, point, ignoreImage, egrad);
819 ELL_4V_SCALE(force, -1, egrad);
820 if (!( AIR_EXISTS(energyOld) && ELL_4V_EXISTS(force) )) {
821 biffAddf(PULL, "%s: point %u non-exist energy or force", me, point->idtag);
822 return 1;
823 }
824 /*
825 if (1560 < task->pctx->iter && 2350 == point->idtag) {
826 printf("!%s(%u): old pos = %g %g %g %g\n", me, point->idtag,
827 point->pos[0], point->pos[1],
828 point->pos[2], point->pos[3]);
829 printf("!%s(%u): energyOld = %g; force = %g %g %g %g\n", me,
830 point->idtag, energyOld, force[0], force[1], force[2], force[3]);
831 }
832 */
833 if (!ELL_4V_DOT(force, force)) {
834 /* this particle has no reason to go anywhere; BUT we still have to
835 enforce constraint if we have one */
836 int constrFail;
837 if (task->pctx->constraint) {
838 if (_pullConstraintSatisfy(task, point,
839 100.0*_PULL_CONSTRAINT_TRAVEL_MAX,
840 &constrFail)) {
841 biffAddf(PULL, "%s: trouble", me);
842 return 1;
843 }
844 }
845 if (constrFail) {
846 /*
847 biffAddf(PULL, "%s: couldn't satisfy constraint on unforced %u: %s",
848 me, point->idtag, airEnumStr(pullConstraintFail, constrFail));
849 return 1;
850 */
851 fprintf(stderr, "%s: *** no constr sat on unfrced %u: %s (si# %u;%u)\n",
852 me, point->idtag, airEnumStr(pullConstraintFail, constrFail),
853 point->stuckIterNum, task->pctx->iterParm.stuckMax);
854 point->status |= PULL_STATUS_STUCK_BIT;
855 point->stuckIterNum += 1;
856 if (task->pctx->iterParm.stuckMax
857 && point->stuckIterNum > task->pctx->iterParm.stuckMax) {
858 point->status |= PULL_STATUS_NIXME_BIT;
859 }
860 }
861 point->energy = energyOld;
862 return 0;
863 }
864
865 if (task->pctx->constraint
866 && (task->pctx->ispec[pullInfoTangent1]
867 || task->pctx->ispec[pullInfoNegativeTangent1])) {
868 /* we have a constraint, so do something to get the force more
869 tangential to the constraint manifold (only in the spatial axes) */
870 double proj[9], pfrc[3];
871 _pullConstraintTangent(task, point, proj);
872 ELL_3MV_MUL(pfrc, proj, force);
873 ELL_3V_COPY(force, pfrc);
874 /* force[3] untouched */
875 }
876 /*
877 if (1560 < task->pctx->iter && 2350 == point->idtag) {
878 printf("!%s(%u): post-constraint tan: force = %g %g %g %g\n", me,
879 point->idtag, force[0], force[1], force[2], force[3]);
880 printf(" precap stepEnergy = %g\n", point->stepEnergy);
881 }
882 */
883 /* Cap particle motion. The point is only allowed to move at most unit
884 distance in rs-normalized space, which may mean that motion in r
885 or s is effectively cramped by crowding in the other axis, oh well.
886 Also, particle motion is limited in terms of spatial voxel size,
887 and (if haveScale) the average distance between scale samples */
888 if (1) {
889 double capvec[4], spcLen, sclLen, max, distLimit;
890
891 /* limits based on distLimit in rs-normalized space */
892 distLimit = _pullDistLimit(task, point);
893 ELL_4V_SCALE(capvec, point->stepEnergy, force);
894 spcLen = ELL_3V_LEN(capvec)/task->pctx->sysParm.radiusSpace;
895 sclLen = AIR_ABS(capvec[3])/task->pctx->sysParm.radiusScale;
896 max = AIR_MAX(spcLen, sclLen);
897 if (max > distLimit) {
898 point->stepEnergy *= distLimit/max;
899 }
900 /* limits based on voxelSizeSpace and voxelSizeScale */
901 ELL_4V_SCALE(capvec, point->stepEnergy, force);
902 spcLen = ELL_3V_LEN(capvec)/task->pctx->voxelSizeSpace;
903 if (task->pctx->haveScale) {
904 sclLen = AIR_ABS(capvec[3])/task->pctx->voxelSizeScale;
905 max = AIR_MAX(spcLen, sclLen);
906 } else {
907 max = spcLen;
908 }
909 if (max > _PULL_DIST_CAP_VOXEL) {
910 point->stepEnergy *= _PULL_DIST_CAP_VOXEL/max;
911 }
912 }
913 /*
914 if (1560 < task->pctx->iter && 2350 == point->idtag) {
915 printf(" postcap stepEnergy = %g\n", point->stepEnergy);
916 }
917 */
918 /* turn off stuck bit, will turn it on again if needed */
919 point->status &= ~PULL_STATUS_STUCK_BIT;
920 ELL_4V_COPY(posOld, point->pos);
921 _pullPointHistInit(point);
922 _pullPointHistAdd(point, pullCondOld);
923 /* try steps along force until we succcessfully lower energy */
924 hailMary = AIR_FALSE;
925 do {
926 int constrFail, energyIncr;
927 giveUp = AIR_FALSE;
928 ELL_4V_SCALE_ADD2(point->pos, 1.0, posOld,
929 point->stepEnergy, force);
930 /*
931 if (1560 < task->pctx->iter && 2350 == point->idtag) {
932 printf("!%s(%u): (iter %u) try pos = %g %g %g %g%s\n",
933 me, point->idtag, task->pctx->iter,
934 point->pos[0], point->pos[1],
935 point->pos[2], point->pos[3],
936 hailMary ? " (Hail Mary)" : "");
937 }
938 */
939 if (task->pctx->haveScale) {
940 point->pos[3] = AIR_CLAMP(task->pctx->bboxMin[3],
941 point->pos[3],
942 task->pctx->bboxMax[3]);
943 }
944 task->pctx->count[pullCountTestStep] += 1;
945 _pullPointHistAdd(point, pullCondEnergyTry);
946 if (task->pctx->constraint) {
947 if (_pullConstraintSatisfy(task, point,
948 _PULL_CONSTRAINT_TRAVEL_MAX,
949 &constrFail)) {
950 biffAddf(PULL, "%s: trouble", me);
951 return 1;
952 }
953 } else {
954 constrFail = AIR_FALSE;
955 }
956 /*
957 if (1560 < task->pctx->iter && 2350 == point->idtag) {
958 printf("!%s(%u): post constr = %g %g %g %g (%d)\n", me,
959 point->idtag,
960 point->pos[0], point->pos[1],
961 point->pos[2], point->pos[3], constrFail);
962 }
963 */
964 if (constrFail) {
965 energyNew = AIR_NAN;
966 } else {
967 energyNew = _pullPointEnergyTotal(task, bin, point, ignoreImage, NULL);
968 }
969 energyIncr = energyNew > (energyOld
970 + task->pctx->sysParm.energyIncreasePermit);
971 /*
972 if (1560 < task->pctx->iter && 2350 == point->idtag) {
973 printf("!%s(%u): constrFail %d; enr Old New = %g %g -> enrIncr %d\n",
974 me, point->idtag, constrFail, energyOld, energyNew, energyIncr);
975 }
976 */
977 stepBad = (constrFail || energyIncr);
978 if (stepBad) {
979 point->stepEnergy *= task->pctx->sysParm.backStepScale;
980 if (constrFail) {
981 _pullPointHistAdd(point, pullCondConstraintFail);
982 } else {
983 _pullPointHistAdd(point, pullCondEnergyBad);
984 }
985 /* you have a problem if you had a non-trivial force, but you can't
986 ever seem to take a small enough step to reduce energy */
987 if (point->stepEnergy < 0.00000001) {
988 /* this can happen if the force is due to a derivative of
989 feature strength with respect to scale, which is measured
990 WITHOUT enforcing the constraint, while particle updates
991 are done WITH the constraint, in which case the computed
992 force can be completely misleading. Thus, as a last-ditch
993 effort, we try moving in the opposite direction (against
994 the force) to see if that helps */
995 if (task->pctx->verbose > 1) {
996 printf("%s: %u %s (%u); (%g,%g,%g,%g) stepEnr %g\n", me,
997 point->idtag, hailMary ? "STUCK!" : "stuck?",
998 point->stuckIterNum,
999 point->pos[0], point->pos[1], point->pos[2], point->pos[3],
1000 point->stepEnergy);
1001 }
1002 if (!hailMary) {
1003 ELL_4V_SCALE(force, -1, force);
1004 /*
1005 if (4819 == point->idtag || 4828 == point->idtag) {
1006 printf("!%s(%u): force now %g %g %g %g\n", me, point->idtag,
1007 force[0], force[1], force[2], force[3]);
1008 }
1009 */
1010 hailMary = AIR_TRUE;
1011 } else {
1012 /* The hail Mary pass missed too; something is really odd.
1013 This can happen when the previous iteration did a sloppy job
1014 enforcing the constraint, so before we move on, we enforce
1015 it, twice for good measure, so that things may be better next
1016 time around */
1017 if (task->pctx->constraint) {
1018 if (_pullConstraintSatisfy(task, point,
1019 _PULL_CONSTRAINT_TRAVEL_MAX,
1020 &constrFail)
1021 || _pullConstraintSatisfy(task, point,
1022 _PULL_CONSTRAINT_TRAVEL_MAX,
1023 &constrFail)) {
1024 biffAddf(PULL, "%s: trouble", me);
1025 return 1;
1026 }
1027 }
1028 energyNew = _pullPointEnergyTotal(task, bin, point,
1029 ignoreImage, NULL);
1030 point->stepEnergy = task->pctx->sysParm.stepInitial;
1031 point->status |= PULL_STATUS_STUCK_BIT;
1032 point->stuckIterNum += 1;
1033 giveUp = AIR_TRUE;
1034 }
1035 }
1036 }
1037 } while (stepBad && !giveUp);
1038 /* Hail Mary worked if (hailMary && !stepBad). It does sometimes work. */
1039
1040 /* now: unless we gave up, energy decreased, and,
1041 if we have one, constraint has been met */
1042 /*
1043 if (1560 < task->pctx->iter && 2350 == point->idtag) {
1044 printf("!%s(%u):iter %u changed (%g,%g,%g,%g)->(%g,%g,%g,%g)\n",
1045 me, point->idtag, task->pctx->iter,
1046 posOld[0], posOld[1], posOld[2], posOld[3],
1047 point->pos[0], point->pos[1], point->pos[2], point->pos[3]);
1048 }
1049 */
1050 _pullPointHistAdd(point, pullCondNew);
1051 ELL_4V_COPY(point->force, force);
1052
1053 /* not recorded for the sake of this function, but for system accounting */
1054 point->energy = energyNew;
1055 if (!AIR_EXISTS(energyNew)) {
1056 biffAddf(PULL, "%s: point %u has non-exist final energy %g\n",
1057 me, point->idtag, energyNew);
1058 return 1;
1059 }
1060
1061 /* if its not stuck, reset stuckIterNum */
1062 if (!(point->status & PULL_STATUS_STUCK_BIT)) {
1063 point->stuckIterNum = 0;
1064 } else if (task->pctx->iterParm.stuckMax
1065 && point->stuckIterNum > task->pctx->iterParm.stuckMax) {
1066 /* else if it is stuck then its up to us to set NIXME
1067 based on point->stuckIterNum */
1068 point->status |= PULL_STATUS_NIXME_BIT;
1069 }
1070
1071 return 0;
1072 }
1073
1074 int
_pullPointProcess(pullTask * task,pullBin * bin,pullPoint * point)1075 _pullPointProcess(pullTask *task, pullBin *bin, pullPoint *point) {
1076 static const char me[]="_pullPointProcess";
1077 int E;
1078
1079 E = 0;
1080 switch (task->processMode) {
1081 case pullProcessModeDescent:
1082 E = _pullPointProcessDescent(task, bin, point,
1083 !task->pctx->haveScale /* ignoreImage */);
1084 break;
1085 case pullProcessModeNeighLearn:
1086 E = _pullPointProcessNeighLearn(task, bin, point);
1087 break;
1088 case pullProcessModeAdding:
1089 if (!task->pctx->flag.noAdd) {
1090 E = _pullPointProcessAdding(task, bin, point);
1091 }
1092 break;
1093 case pullProcessModeNixing:
1094 E = _pullPointProcessNixing(task, bin, point);
1095 break;
1096 default:
1097 biffAddf(PULL, "%s: process mode %d unrecognized", me, task->processMode);
1098 return 1;
1099 break;
1100 }
1101 if (E) {
1102 biffAddf(PULL, "%s: trouble", me);
1103 return 1;
1104 }
1105 return 0;
1106 }
1107
1108 int
pullBinProcess(pullTask * task,unsigned int myBinIdx)1109 pullBinProcess(pullTask *task, unsigned int myBinIdx) {
1110 static const char me[]="pullBinProcess";
1111 pullBin *myBin;
1112 unsigned int myPointIdx;
1113
1114 if (task->pctx->verbose > 2) {
1115 printf("%s(%s): doing bin %u\n", me,
1116 airEnumStr(pullProcessMode, task->processMode), myBinIdx);
1117 }
1118 myBin = task->pctx->bin + myBinIdx;
1119 for (myPointIdx=0; myPointIdx<myBin->pointNum; myPointIdx++) {
1120 pullPoint *point;
1121 if (task->pctx->pointNum > _PULL_PROGRESS_POINT_NUM_MIN
1122 && !task->pctx->flag.binSingle
1123 && task->pctx->progressBinMod
1124 && 0 == myBinIdx % task->pctx->progressBinMod) {
1125 printf("."); fflush(stdout);
1126 }
1127 point = myBin->point[myPointIdx];
1128 if (task->pctx->verbose > 2) {
1129 printf("%s(%s) processing (bin %u)->point[%u] %u\n", me,
1130 airEnumStr(pullProcessMode, task->processMode),
1131 myBinIdx, myPointIdx, point->idtag);
1132 }
1133 if (_pullPointProcess(task, myBin, point)) {
1134 biffAddf(PULL, "%s: on point %u of bin %u\n", me,
1135 myPointIdx, myBinIdx);
1136 return 1;
1137 }
1138 task->stuckNum += (point->status & PULL_STATUS_STUCK_BIT);
1139 } /* for myPointIdx */
1140
1141 return 0;
1142 }
1143
1144 int
pullGammaLearn(pullContext * pctx)1145 pullGammaLearn(pullContext *pctx) {
1146 static const char me[]="pullGammaLearn";
1147 unsigned int binIdx, pointIdx, pointNum;
1148 pullBin *bin;
1149 pullPoint *point;
1150 pullTask *task;
1151 double deltaScale, scl, beta, wellX=0, wellY=0,
1152 *strdd, *gmag, meanGmag, meanStrdd, wght, wghtSum;
1153 airArray *mop;
1154
1155 if (!pctx) {
1156 biffAddf(PULL, "%s: got NULL pointer", me);
1157 return 1;
1158 }
1159 if (!pctx->haveScale) {
1160 biffAddf(PULL, "%s: not using scale-space", me);
1161 return 1;
1162 }
1163 if (pullInterTypeAdditive == pctx->interType) {
1164 if (pullEnergyButterworthParabola != pctx->energySpecS->energy) {
1165 biffAddf(PULL, "%s: want %s energy along scale, not %s", me,
1166 pullEnergyButterworthParabola->name,
1167 pctx->energySpecS->energy->name);
1168 return 1;
1169 }
1170 } else if (pullInterTypeSeparable == pctx->interType) {
1171 wellY = pctx->energySpecR->energy->well(&wellX,
1172 pctx->energySpecR->parm);
1173 if (!( wellY < 0 )) {
1174 biffAddf(PULL, "%s: spatial energy %s didn't have well",
1175 me, pctx->energySpecR->energy->name);
1176 return 1;
1177 }
1178 if (pullEnergyBspln != pctx->energySpecS->energy) {
1179 biffAddf(PULL, "%s: want %s energy along scale, not %s", me,
1180 pullEnergyBspln->name,
1181 pctx->energySpecS->energy->name);
1182 return 1;
1183 }
1184 } else {
1185 biffAddf(PULL, "%s: need %s or %s inter type, not %s", me,
1186 airEnumStr(pullInterType, pullInterTypeAdditive),
1187 airEnumStr(pullInterType, pullInterTypeSeparable),
1188 airEnumStr(pullInterType, pctx->interType));
1189 return 1;
1190 }
1191 pointNum = pullPointNumber(pctx);
1192 if (!pointNum) {
1193 biffAddf(PULL, "%s: had no points!", me);
1194 return 1;
1195 }
1196
1197 mop = airMopNew();
1198 strdd = AIR_CALLOC(pointNum, double);
1199 airMopAdd(mop, strdd, airFree, airMopAlways);
1200 gmag = AIR_CALLOC(pointNum, double);
1201 airMopAdd(mop, gmag, airFree, airMopAlways);
1202 if (!(strdd && gmag)) {
1203 biffAddf(PULL, "%s: couldn't alloc two buffers of %u doubles",
1204 me, pointNum);
1205 airMopError(mop);
1206 return 1;
1207 }
1208
1209 task = pctx->task[0];
1210 pointIdx = 0;
1211 deltaScale = pctx->bboxMax[3] - pctx->bboxMin[3];
1212 deltaScale *= _PULL_STRENGTH_ENERGY_DELTA_SCALE;
1213 for (binIdx=0; binIdx<pctx->binNum; binIdx++) {
1214 unsigned int pidx;
1215 bin = pctx->bin + binIdx;
1216 for (pidx=0; pidx<bin->pointNum; pidx++) {
1217 double str[3], _ss, _gr;
1218 point = bin->point[pidx];
1219 point->pos[3] += deltaScale;
1220 pullProbe(task, point);
1221 str[2] = pullPointScalar(pctx, point, pullInfoStrength,
1222 NULL, NULL);
1223 point->pos[3] -= 2*deltaScale;
1224 pullProbe(task, point);
1225 str[0] = pullPointScalar(pctx, point, pullInfoStrength,
1226 NULL, NULL);
1227 point->pos[3] += deltaScale;
1228 pullProbe(task, point);
1229 str[1] = pullPointScalar(pctx, point, pullInfoStrength,
1230 NULL, NULL);
1231 _ss = (str[0] - 2*str[1] + str[2])/(deltaScale*deltaScale);
1232 if (_ss < 0.0) {
1233 _gr = (str[2] - str[0])/(2*deltaScale);
1234 _gr = AIR_ABS(_gr);
1235 strdd[pointIdx] = _ss;
1236 gmag[pointIdx] = _gr;
1237 pointIdx++;
1238 }
1239 }
1240 }
1241 if (!pointIdx) {
1242 biffAddf(PULL, "%s: no points w/ 2nd deriv of strn wrt scale < 0", me);
1243 airMopError(mop);
1244 return 1;
1245 }
1246
1247 /* resetting pointNum to actual number of points used */
1248 pointNum = pointIdx;
1249 /* learn meanGmag, with sqrt() sneakiness to discount high gmags */
1250 meanGmag = 0.0;
1251 for (pointIdx=0; pointIdx<pointNum; pointIdx++) {
1252 meanGmag += sqrt(gmag[pointIdx]);
1253 }
1254 meanGmag /= pointNum;
1255 meanGmag *= meanGmag;
1256 /* learn meanStrdd with a Gaussian weight on gmag; we want
1257 to give more weight to the strdds that are near maximal strength
1258 (hence 1st derivative near zero) */
1259 meanStrdd = wghtSum = 0.0;
1260 for (pointIdx=0; pointIdx<pointNum; pointIdx++) {
1261 /* the "meanGmag/8" allowed the gamma learned from a
1262 cone dataset immediately post-initialization to
1263 nearly match the gamma learned post-phase-2 */
1264 wght = airGaussian(gmag[pointIdx], 0.0, meanGmag/8);
1265 wghtSum += wght;
1266 meanStrdd += wght*strdd[pointIdx];
1267 }
1268 meanStrdd /= wghtSum;
1269
1270 scl = pctx->sysParm.radiusScale;
1271 if (pullInterTypeAdditive == pctx->interType) {
1272 /* want to satisfy str''(s) = enr''(s)
1273 ** ==> -gamma*strdd = 2*beta/(radiusScale)^2
1274 ** ==> gamma = -2*beta/(strdd*(radiusScale)^2)
1275 ** (beta = 1) ==> gamma = -2/(strdd*(radiusScale)^2)
1276 ** NOTE: The difference from what is in the paper is a factor of 2,
1277 ** and the ability to include the influence of beta
1278 */
1279 beta = (pctx->flag.useBetaForGammaLearn
1280 ? pctx->sysParm.beta
1281 : 1.0);
1282 pctx->sysParm.gamma = -2*beta/(meanStrdd*scl*scl);
1283 } else if (pullInterTypeSeparable == pctx->interType) {
1284 /* want to satisfy str''(s) = enr''(s); wellY < 0
1285 ** ==> gamma*strdd = wellY*8/(radiusScale)^2
1286 ** gamma = wellY*8/(strdd*(radiusScale)^2)
1287 */
1288 pctx->sysParm.gamma = wellY*8/(meanStrdd*scl*scl);
1289 pctx->sysParm.gamma *= pctx->sysParm.separableGammaLearnRescale;
1290 } else {
1291 biffAddf(PULL, "%s: sorry %s inter type unimplemented", me,
1292 airEnumStr(pullInterType, pctx->interType));
1293 airMopError(mop);
1294 return 1;
1295 }
1296 if (pctx->verbose) {
1297 printf("%s: learned gamma %g\n", me, pctx->sysParm.gamma);
1298 }
1299
1300 airMopOkay(mop);
1301 return 0;
1302 }
1303
1304