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