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