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 int
_pullPointProcessNeighLearn(pullTask * task,pullBin * bin,pullPoint * point)29 _pullPointProcessNeighLearn(pullTask *task, pullBin *bin, pullPoint *point) {
30 
31   /* sneaky: we just learn (and discard) the energy, and this function
32      will do the work of learning the neighbors */
33   _pullEnergyFromPoints(task, bin, point, NULL);
34   return 0;
35 }
36 
37 static double
_pointEnergyOfNeighbors(pullTask * task,pullBin * bin,pullPoint * point,double * fracNixed)38 _pointEnergyOfNeighbors(pullTask *task, pullBin *bin, pullPoint *point,
39                         double *fracNixed) {
40   double enr;
41   unsigned int ii, xx;
42   pullPoint *her;
43 
44   enr = 0;
45   xx = 0;
46   for (ii=0; ii<point->neighPointNum; ii++) {
47     her = point->neighPoint[ii];
48     if (her->status & PULL_STATUS_NIXME_BIT) {
49       xx += 1;
50     } else {
51       enr += _pullEnergyFromPoints(task, bin, her, NULL);
52     }
53   }
54   *fracNixed = (point->neighPointNum
55                 ? AIR_CAST(double, xx)/point->neighPointNum
56                 : 0);
57   return enr;
58 }
59 
60 int
_pullPointProcessAdding(pullTask * task,pullBin * bin,pullPoint * point)61 _pullPointProcessAdding(pullTask *task, pullBin *bin, pullPoint *point) {
62   static const char me[]="_pullPointProcessAdding";
63   unsigned int npi, iter, api;
64   double noffavg[4], npos[4], enrWith, enrWithout,
65     fracNixed, newSpcDist, tmp;
66   pullPoint *newpnt;
67   int E;
68 
69   task->pctx->count[pullCountAdding] += 1;
70 
71   if (point->neighPointNum && task->pctx->targetDim
72       && task->pctx->flag.popCntlEnoughTest) {
73     unsigned int plenty;
74     plenty = (1 == task->pctx->targetDim
75               ? 3
76               : (2 == task->pctx->targetDim
77                  ? 7
78                  : (3 == task->pctx->targetDim
79                     ? 13 /* = 1 + 12
80                             = 1 + coordination number of 3D sphere packing */
81                     : 0 /* shouldn't get here */)));
82     /*
83     if (0 == (point->idtag % 100)) {
84       printf("%s: #num %d >?= plenty %d\n", me, point->neighPointNum, plenty);
85     }
86     */
87     if (point->neighPointNum >= plenty) {
88       /* there's little chance that adding points will reduce energy */
89       return 0;
90     }
91   }
92   ELL_4V_SET(noffavg, 0, 0, 0, 0);
93   for (npi=0; npi<point->neighPointNum; npi++) {
94     double off[4];
95     ELL_4V_SUB(off, point->pos, point->neighPoint[npi]->pos);
96     /* normalize the offset */
97     ELL_3V_SCALE(off, 1/task->pctx->sysParm.radiusSpace, off);
98     if (task->pctx->haveScale) {
99       off[3] /= task->pctx->sysParm.radiusScale;
100     }
101     ELL_4V_INCR(noffavg, off);
102   }
103   if (point->neighPointNum) {
104     /*
105     if (0 == (point->idtag % 100)) {
106       printf("%s: len(offavg) %g >?= thresh %g\n", me,
107              ELL_4V_LEN(noffavg)/point->neighPointNum,
108              _PULL_NEIGH_OFFSET_SUM_THRESH);
109     }
110     */
111     if (ELL_4V_LEN(noffavg)/point->neighPointNum
112         < _PULL_NEIGH_OFFSET_SUM_THRESH) {
113       /* we have neighbors, and they seem to be balanced well enough;
114          don't try to add */
115       return 0;
116     }
117   }
118   if (task->pctx->energySpecR->energy->well(&newSpcDist,
119                                             task->pctx->energySpecR->parm)) {
120     /* HEY: if we don't actually have a well, what is the point of
121        guessing a new distance? */
122     newSpcDist = _PULL_NEWPNT_DIST;
123   }
124   /* compute offset (normalized) direction from current point location */
125   if (!point->neighPointNum) {
126     /* we had no neighbors, have to pretend like we did */
127     airNormalRand_r(noffavg + 0, noffavg + 1, task->rng);
128     airNormalRand_r(noffavg + 2, noffavg + 3, task->rng);
129     if (!task->pctx->haveScale) {
130       noffavg[3] = 0;
131     }
132   }
133   if (task->pctx->constraint) {
134     double proj[9], tmpvec[3];
135     _pullConstraintTangent(task, point, proj);
136     ELL_3MV_MUL(tmpvec, proj, noffavg);
137     ELL_3V_COPY(noffavg, tmpvec);
138   }
139   ELL_4V_NORM(noffavg, noffavg, tmp);
140   ELL_3V_SCALE(noffavg, task->pctx->sysParm.radiusSpace, noffavg);
141   noffavg[3] *= task->pctx->sysParm.radiusScale;
142   ELL_4V_SCALE(noffavg, newSpcDist, noffavg);
143   /* set new point location */
144   ELL_4V_ADD2(npos, noffavg, point->pos);
145   if (!_pullInsideBBox(task->pctx, npos)) {
146     if (task->pctx->verbose > 2) {
147       printf("%s: new pnt would start (%g,%g,%g,%g) outside bbox, nope\n",
148              me, npos[0], npos[1], npos[2], npos[3]);
149     }
150     return 0;
151   }
152   /* initial pos is good, now we start getting serious */
153   newpnt = pullPointNew(task->pctx);
154   if (!newpnt) {
155     biffAddf(PULL, "%s: couldn't spawn new point from %u", me, point->idtag);
156     return 1;
157   }
158   ELL_4V_COPY(newpnt->pos, npos);
159   /* set status to indicate this is an unbinned point, with no
160      knowledge of its neighbors */
161   newpnt->status |= PULL_STATUS_NEWBIE_BIT;
162   /* satisfy constraint if needed */
163   if (task->pctx->constraint) {
164     int constrFail;
165     if (_pullConstraintSatisfy(task, newpnt,
166                                _PULL_CONSTRAINT_TRAVEL_MAX,
167                                &constrFail)) {
168       biffAddf(PULL, "%s: on newbie point %u (spawned from %u)", me,
169                newpnt->idtag, point->idtag);
170       pullPointNix(newpnt); return 1;
171     }
172     if (constrFail) {
173       /* constraint satisfaction failed, which isn't an error for us,
174          we just don't try to add this point.  Can do immediate nix
175          because no neighbors know about this point. */
176       pullPointNix(newpnt);
177       return 0;
178     }
179     if (!_pullInsideBBox(task->pctx, newpnt->pos)) {
180       if (task->pctx->verbose > 2) {
181         printf("%s: post constr newpnt %u (%g,%g,%g,%g) outside bbox; nope\n",
182                me, newpnt->idtag, newpnt->pos[0], newpnt->pos[1],
183                newpnt->pos[2], newpnt->pos[3]);
184       }
185       newpnt = pullPointNix(newpnt);
186       return 0;
187     }
188   }
189   /* do some descent, on this point only, which (HACK!) we do by
190      changing the per-task process mode . . . */
191   task->processMode = pullProcessModeDescent;
192   E = 0;
193   for (iter=0; iter<task->pctx->iterParm.addDescent; iter++) {
194     double diff[4];
195     if (!E) E |= _pullPointProcessDescent(task, bin, newpnt,
196                                           /* ignoreImage; actually it is
197                                            this use which motivated the
198                                            creation of ignoreImage */
199                                           AIR_TRUE);
200     if (newpnt->status & PULL_STATUS_STUCK_BIT) {
201       if (task->pctx->verbose > 2) {
202         printf("%s: possible newpnt %u stuck @ iter %u; nope\n", me,
203                newpnt->idtag, iter);
204       }
205       newpnt = pullPointNix(newpnt);
206       /* if we don't change the mode back, then pullBinProcess() won't
207          know to try adding for the rest of the bins it sees, bad HACK */
208       task->processMode = pullProcessModeAdding;
209       return 0;
210     }
211     if (!_pullInsideBBox(task->pctx, newpnt->pos)) {
212       if (task->pctx->verbose > 2) {
213         printf("%s: newpnt %u went (%g,%g,%g,%g) outside bbox; nope\n",
214                me, newpnt->idtag, newpnt->pos[0], newpnt->pos[1],
215                newpnt->pos[2], newpnt->pos[3]);
216       }
217       newpnt = pullPointNix(newpnt);
218       task->processMode = pullProcessModeAdding;
219       return 0;
220     }
221     ELL_4V_SUB(diff, newpnt->pos, point->pos);
222     ELL_3V_SCALE(diff, 1/task->pctx->sysParm.radiusSpace, diff);
223     diff[3] /= task->pctx->sysParm.radiusScale;
224     if (ELL_4V_LEN(diff) > _PULL_NEWPNT_STRAY_DIST) {
225       if (task->pctx->verbose > 2) {
226         printf("%s: newpnt %u went too far %g from old point %u; nope\n",
227                me, newpnt->idtag, ELL_4V_LEN(diff), point->idtag);
228       }
229       newpnt = pullPointNix(newpnt);
230       task->processMode = pullProcessModeAdding;
231       return 0;
232     }
233     /* still okay to continue descending */
234     newpnt->stepEnergy *= task->pctx->sysParm.opporStepScale;
235   }
236   /* now that newbie point is final test location, see if it meets
237      the live thresh, if there is one */
238   if (task->pctx->ispec[pullInfoLiveThresh]
239       && 0 > pullPointScalar(task->pctx, newpnt, pullInfoLiveThresh,
240                              NULL, NULL)) {
241     /* didn't meet threshold */
242     newpnt = pullPointNix(newpnt);
243     task->processMode = pullProcessModeAdding;
244     return 0;
245   }
246   if (task->pctx->ispec[pullInfoLiveThresh2] /* HEY: copy & paste */
247       && 0 > pullPointScalar(task->pctx, newpnt, pullInfoLiveThresh2,
248                              NULL, NULL)) {
249     /* didn't meet threshold */
250     newpnt = pullPointNix(newpnt);
251     task->processMode = pullProcessModeAdding;
252     return 0;
253   }
254   if (task->pctx->ispec[pullInfoLiveThresh3] /* HEY: copy & paste */
255       && 0 > pullPointScalar(task->pctx, newpnt, pullInfoLiveThresh3,
256                              NULL, NULL)) {
257     /* didn't meet threshold */
258     newpnt = pullPointNix(newpnt);
259     task->processMode = pullProcessModeAdding;
260     return 0;
261   }
262   /* see if the new point should be nixed because its at a volume edge */
263   if (task->pctx->flag.nixAtVolumeEdgeSpace
264       && (newpnt->status & PULL_STATUS_EDGE_BIT)) {
265     newpnt = pullPointNix(newpnt);
266     task->processMode = pullProcessModeAdding;
267     return 0;
268   }
269   /* no problem with live thresh, test energy by first learn neighbors */
270   /* we have to add newpnt to task's add queue, just so that its neighbors
271      can see it as a possible neighbor */
272   api = airArrayLenIncr(task->addPointArr, 1);
273   task->addPoint[api] = newpnt;
274   task->processMode = pullProcessModeNeighLearn;
275   _pullEnergyFromPoints(task, bin, newpnt, NULL);
276   /* and teach the neighbors their neighbors (possibly including newpnt) */
277   for (npi=0; npi<newpnt->neighPointNum; npi++) {
278     _pullEnergyFromPoints(task, bin, newpnt->neighPoint[npi], NULL);
279   }
280   /* now back to the actual mode we came here with */
281   task->processMode = pullProcessModeAdding;
282   enrWith = (_pullEnergyFromPoints(task, bin, newpnt, NULL)
283              + _pointEnergyOfNeighbors(task, bin, newpnt, &fracNixed));
284   newpnt->status |= PULL_STATUS_NIXME_BIT;  /* turn nixme on */
285   enrWithout = _pointEnergyOfNeighbors(task, bin, newpnt, &fracNixed);
286   newpnt->status &= ~PULL_STATUS_NIXME_BIT; /* turn nixme off */
287   if (enrWith < enrWithout) {
288     /* energy is clearly lower *with* newpnt, so we want to add it, which
289        means keeping it in the add queue where it already is */
290     if (task->pctx->logAdd) {
291       double posdiff[4];
292       ELL_4V_SUB(posdiff, newpnt->pos, point->pos);
293       fprintf(task->pctx->logAdd, "%u %g %g %g %g %g %g\n", newpnt->idtag,
294               ELL_3V_LEN(posdiff)/task->pctx->sysParm.radiusSpace,
295               AIR_ABS(posdiff[3])/task->pctx->sysParm.radiusScale,
296               newpnt->pos[0], newpnt->pos[1], newpnt->pos[2], newpnt->pos[3]);
297     }
298   } else {
299     /* adding point is not an improvement, remove it from the add queue */
300     airArrayLenIncr(task->addPointArr, -1);
301     /* ugh, have to signal to neighbors that its no longer their neighbor.
302        HEY this is the part that is totally screwed with multiple threads */
303     task->processMode = pullProcessModeNeighLearn;
304     newpnt->status |= PULL_STATUS_NIXME_BIT;
305     for (npi=0; npi<newpnt->neighPointNum; npi++) {
306       _pullEnergyFromPoints(task, bin, newpnt->neighPoint[npi], NULL);
307     }
308     task->processMode = pullProcessModeAdding;
309     newpnt = pullPointNix(newpnt);
310   }
311   return 0;
312 }
313 
314 int
_pullPointProcessNixing(pullTask * task,pullBin * bin,pullPoint * point)315 _pullPointProcessNixing(pullTask *task, pullBin *bin, pullPoint *point) {
316   double enrWith, enrNeigh, enrWithout, fracNixed;
317 
318   task->pctx->count[pullCountNixing] += 1;
319 
320   /* if there's a live thresh, do we meet it? */
321   if (task->pctx->ispec[pullInfoLiveThresh]
322       && 0 > pullPointScalar(task->pctx, point, pullInfoLiveThresh,
323                              NULL, NULL)) {
324     point->status |= PULL_STATUS_NIXME_BIT;
325     return 0;
326   }
327   /* HEY copy & paste */
328   if (task->pctx->ispec[pullInfoLiveThresh2]
329       && 0 > pullPointScalar(task->pctx, point, pullInfoLiveThresh2,
330                              NULL, NULL)) {
331     point->status |= PULL_STATUS_NIXME_BIT;
332     return 0;
333   }
334   /* HEY copy & paste */
335   if (task->pctx->ispec[pullInfoLiveThresh3]
336       && 0 > pullPointScalar(task->pctx, point, pullInfoLiveThresh3,
337                              NULL, NULL)) {
338     point->status |= PULL_STATUS_NIXME_BIT;
339     return 0;
340   }
341 
342   /* if many neighbors have been nixed, then system is far from convergence,
343      so energy is not a very meaningful guide to whether to nix this point
344      NOTE that we use this function to *learn* fracNixed */
345   enrNeigh = _pointEnergyOfNeighbors(task, bin, point, &fracNixed);
346   if (fracNixed < task->pctx->sysParm.fracNeighNixedMax) {
347     /* is energy lower without us around? */
348     enrWith = enrNeigh + _pullEnergyFromPoints(task, bin, point, NULL);
349     point->status |= PULL_STATUS_NIXME_BIT;    /* turn nixme on */
350     enrWithout = _pointEnergyOfNeighbors(task, bin, point, &fracNixed);
351     if (enrWith <= enrWithout) {
352       /* Energy isn't distinctly lowered without the point, so keep it;
353          turn off nixing.  If enrWith == enrWithout == 0, as happens to
354          isolated points, then the difference between "<=" and "<"
355          keeps the isolated points from getting nixed */
356       point->status &= ~PULL_STATUS_NIXME_BIT; /* turn nixme off */
357     }
358     /* else energy is certainly higher with the point, do nix it */
359   }
360 
361   return 0;
362 }
363 
364 int
_pullIterFinishNeighLearn(pullContext * pctx)365 _pullIterFinishNeighLearn(pullContext *pctx) {
366   static const char me[]="_pullIterFinishNeighLearn";
367 
368   /* a no-op for now */
369   AIR_UNUSED(pctx);
370   AIR_UNUSED(me);
371 
372   return 0;
373 }
374 
375 int
_pullIterFinishAdding(pullContext * pctx)376 _pullIterFinishAdding(pullContext *pctx) {
377   static const char me[]="_pullIterFinishAdding";
378   unsigned int taskIdx;
379 
380   pctx->addNum = 0;
381   for (taskIdx=0; taskIdx<pctx->threadNum; taskIdx++) {
382     pullTask *task;
383     task = pctx->task[taskIdx];
384     if (task->addPointNum) {
385       unsigned int pointIdx;
386       int added;
387       for (pointIdx=0; pointIdx<task->addPointNum; pointIdx++) {
388         pullPoint *point;
389         pullBin *bin;
390         point = task->addPoint[pointIdx];
391         point->status &= ~PULL_STATUS_NEWBIE_BIT;
392         if (pullBinsPointMaybeAdd(pctx, point, &bin, &added)) {
393           biffAddf(PULL, "%s: trouble binning new point %u", me, point->idtag);
394           return 1;
395         }
396         if (added) {
397           pctx->addNum++;
398         } else {
399           unsigned int npi, xpi;
400           if (pctx->verbose) {
401             printf("%s: decided NOT to add new point %u\n", me, point->idtag);
402           }
403           /* HEY: copied from above */
404           /* ugh, have to signal to neigs that its no longer their neighbor */
405           task->processMode = pullProcessModeNeighLearn;
406           point->status |= PULL_STATUS_NIXME_BIT;
407           for (npi=0; npi<point->neighPointNum; npi++) {
408             _pullEnergyFromPoints(task, bin, point->neighPoint[npi], NULL);
409           }
410           task->processMode = pullProcessModeAdding;
411           /* can't do immediate nix for reasons GLK doesn't quite understand */
412           xpi = airArrayLenIncr(task->nixPointArr, 1);
413           task->nixPoint[xpi] = point;
414         }
415       }
416       airArrayLenSet(task->addPointArr, 0);
417     }
418   }
419   if (pctx->verbose && pctx->addNum) {
420     printf("%s: ADDED %u\n", me, pctx->addNum);
421   }
422   return 0;
423 }
424 
425 void
_pullNixTheNixed(pullContext * pctx)426 _pullNixTheNixed(pullContext *pctx) {
427   unsigned int binIdx;
428 
429   pctx->nixNum = 0;
430   for (binIdx=0; binIdx<pctx->binNum; binIdx++) {
431     pullBin *bin;
432     unsigned int pointIdx;
433     bin = pctx->bin + binIdx;
434     pointIdx = 0;
435     while (pointIdx < bin->pointNum) {
436       pullPoint *point;
437       point = bin->point[pointIdx];
438       if (pctx->flag.nixAtVolumeEdgeSpace
439           && (point->status & PULL_STATUS_EDGE_BIT)) {
440         point->status |= PULL_STATUS_NIXME_BIT;
441       }
442       if (point->status & PULL_STATUS_NIXME_BIT) {
443         pullPointNix(point);
444         /* copy last point pointer to this slot */
445         bin->point[pointIdx] = bin->point[bin->pointNum-1];
446         airArrayLenIncr(bin->pointArr, -1); /* will decrement bin->pointNum */
447         pctx->nixNum++;
448       } else {
449         pointIdx++;
450       }
451     }
452   }
453   return;
454 }
455 
456 int
_pullIterFinishNixing(pullContext * pctx)457 _pullIterFinishNixing(pullContext *pctx) {
458   static const char me[]="_pullIterFinishNixing";
459   unsigned int taskIdx;
460 
461   _pullNixTheNixed(pctx);
462   /* finish nixing the things that we decided not to add */
463   for (taskIdx=0; taskIdx<pctx->threadNum; taskIdx++) {
464     pullTask *task;
465     task = pctx->task[taskIdx];
466     if (task->nixPointNum) {
467       unsigned int xpi;
468       for (xpi=0; xpi<task->nixPointNum; xpi++) {
469         pullPointNix(task->nixPoint[xpi]);
470       }
471       airArrayLenSet(task->nixPointArr, 0);
472     }
473   }
474   if (pctx->verbose && pctx->nixNum) {
475     printf("%s: NIXED %u\n", me, pctx->nixNum);
476   }
477   return 0;
478 }
479 
480