GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: src/pull/actionPull.c Lines: 0 543 0.0 %
Date: 2017-05-26 Branches: 0 328 0.0 %

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