source: src/Fragmentation/Exporters/SphericalPointDistribution.cpp@ 7e45c4

Last change on this file since 7e45c4 was 7e45c4, checked in by Frederik Heber <heber@…>, 11 years ago

tempcommit: Modified calculateErrorOfMatching() to measure error as deviation from mean rotation angle.

  • distances between points for a equidistantly distributed set of points on a sphere does not sound too sensible. We now calculate the average rotation angle between the two given sets of points and give the L1/L2 error as maximum and squared average difference to this mean.
  • Property mode set to 100644
File size: 51.9 KB
Line 
1/*
2 * Project: MoleCuilder
3 * Description: creates and alters molecular systems
4 * Copyright (C) 2014 Frederik Heber. All rights reserved.
5 *
6 *
7 * This file is part of MoleCuilder.
8 *
9 * MoleCuilder is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation, either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * MoleCuilder 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
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with MoleCuilder. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23/*
24 * SphericalPointDistribution.cpp
25 *
26 * Created on: May 30, 2014
27 * Author: heber
28 */
29
30// include config.h
31#ifdef HAVE_CONFIG_H
32#include <config.h>
33#endif
34
35#include "CodePatterns/MemDebug.hpp"
36
37#include "SphericalPointDistribution.hpp"
38
39#include "CodePatterns/Assert.hpp"
40#include "CodePatterns/IteratorAdaptors.hpp"
41#include "CodePatterns/Log.hpp"
42#include "CodePatterns/toString.hpp"
43
44#include <algorithm>
45#include <boost/assign.hpp>
46#include <cmath>
47#include <functional>
48#include <iterator>
49#include <limits>
50#include <list>
51#include <numeric>
52#include <vector>
53#include <map>
54
55#include "LinearAlgebra/Line.hpp"
56#include "LinearAlgebra/Plane.hpp"
57#include "LinearAlgebra/RealSpaceMatrix.hpp"
58#include "LinearAlgebra/Vector.hpp"
59
60using namespace boost::assign;
61
62// static entities
63const double SphericalPointDistribution::warn_amplitude = 1e-2;
64const double SphericalPointDistribution::L1THRESHOLD = 1e-2;
65const double SphericalPointDistribution::L2THRESHOLD = 2e-1;
66
67typedef std::vector<double> DistanceArray_t;
68
69// class generator: taken from www.cplusplus.com example std::generate
70struct c_unique {
71 unsigned int current;
72 c_unique() {current=0;}
73 unsigned int operator()() {return current++;}
74} UniqueNumber;
75
76struct c_unique_list {
77 unsigned int current;
78 c_unique_list() {current=0;}
79 std::list<unsigned int> operator()() {return std::list<unsigned int>(1, current++);}
80} UniqueNumberList;
81
82/** Calculate the center of a given set of points in \a _positions but only
83 * for those indicated by \a _indices.
84 *
85 */
86inline
87Vector calculateGeographicMidpoint(
88 const SphericalPointDistribution::VectorArray_t &_positions,
89 const SphericalPointDistribution::IndexList_t &_indices)
90{
91 Vector Center;
92 Center.Zero();
93 for (SphericalPointDistribution::IndexList_t::const_iterator iter = _indices.begin();
94 iter != _indices.end(); ++iter)
95 Center += _positions[*iter];
96 if (!_indices.empty())
97 Center *= 1./(double)_indices.size();
98
99 return Center;
100}
101
102inline
103double calculateMinimumDistance(
104 const Vector &_center,
105 const SphericalPointDistribution::VectorArray_t &_points,
106 const SphericalPointDistribution::IndexList_t & _indices)
107{
108 double MinimumDistance = 0.;
109 for (SphericalPointDistribution::IndexList_t::const_iterator iter = _indices.begin();
110 iter != _indices.end(); ++iter) {
111 const double angle = _center.Angle(_points[*iter]);
112 MinimumDistance += angle*angle;
113 }
114 return sqrt(MinimumDistance);
115}
116
117/** Calculates the center of minimum distance for a given set of points \a _points.
118 *
119 * According to http://www.geomidpoint.com/calculation.html this goes a follows:
120 * -# Let CurrentPoint be the geographic midpoint found in Method A. this is used as the starting point for the search.
121 * -# Let MinimumDistance be the sum total of all distances from the current point to all locations in 'Your Places'.
122 * -# Find the total distance between each location in 'Your Places' and all other locations in 'Your Places'. If any one of these locations has a new smallest distance then that location becomes the new CurrentPoint and MinimumDistance.
123 * -# Let TestDistance be PI/2 radians (6225 miles or 10018 km).
124 * -# Find the total distance between each of 8 test points and all locations in 'Your Places'. The test points are arranged in a circular pattern around the CurrentPoint at a distance of TestDistance to the north, northeast, east, southeast, south, southwest, west and northwest.
125 * -# If any of these 8 points has a new smallest distance then that point becomes the new CurrentPoint and MinimumDistance and go back to step 5 using that point.
126 * -# If none of the 8 test points has a new smallest distance then divide TestDistance by 2 and go back to step 5 using the same point.
127 * -# Repeat steps 5 to 7 until no new smallest distance can be found or until TestDistance is less than 0.00000002 radians.
128 *
129 * \param _points set of points
130 * \return Center of minimum distance for all these points, is always of length 1
131 */
132Vector SphericalPointDistribution::calculateCenterOfMinimumDistance(
133 const SphericalPointDistribution::VectorArray_t &_positions,
134 const SphericalPointDistribution::IndexList_t &_indices)
135{
136 ASSERT( _positions.size() >= _indices.size(),
137 "calculateCenterOfMinimumDistance() - less positions than indices given.");
138 Vector center(1.,0.,0.);
139
140 /// first treat some special cases
141 // no positions given: return x axis vector (NOT zero!)
142 {
143 if (_indices.empty())
144 return center;
145 // one position given: return it directly
146 if (_indices.size() == (size_t)1)
147 return _positions[*_indices.begin()];
148 // two positions on a line given: return closest point to (1.,0.,0.)
149// IndexList_t::const_iterator indexiter = _indices.begin();
150// const unsigned int firstindex = *indexiter++;
151// const unsigned int secondindex = *indexiter;
152// if ( fabs(_positions[firstindex].ScalarProduct(_positions[secondindex]) + 1.)
153// < std::numeric_limits<double>::epsilon()*1e4) {
154// Vector candidate;
155// if (_positions[firstindex].ScalarProduct(center) > _positions[secondindex].ScalarProduct(center))
156// candidate = _positions[firstindex];
157// else
158// candidate = _positions[secondindex];
159// // non-uniqueness: all positions on great circle, normal to given line are valid
160// // so, we just pick one because returning a unique point is topmost priority
161// Vector normal;
162// normal.GetOneNormalVector(candidate);
163// Vector othernormal = candidate;
164// othernormal.VectorProduct(normal);
165// // now both normal and othernormal describe the plane containing the great circle
166// Plane greatcircle(normal, zeroVec, othernormal);
167// // check which axis is contained and pick the one not
168// if (greatcircle.isContained(center)) {
169// center = Vector(0.,1.,0.);
170// if (greatcircle.isContained(center))
171// center = Vector(0.,0.,1.);
172// // now we are done cause a plane cannot contain all three axis vectors
173// }
174// center = greatcircle.getClosestPoint(candidate);
175// // assure length of 1
176// center.Normalize();
177//
178// return center;
179// }
180 // given points lie on a great circle and go completely round it
181 // two or more positions on a great circle given: return closest point to (1.,0.,0.)
182 {
183 bool AllNormal = true;
184 Vector Normal;
185 {
186 IndexList_t::const_iterator indexiter = _indices.begin();
187 Normal = _positions[*indexiter++];
188 Normal.VectorProduct(_positions[*indexiter++]);
189 Normal.Normalize();
190 for (;(AllNormal) && (indexiter != _indices.end()); ++indexiter)
191 AllNormal &= _positions[*indexiter].IsNormalTo(Normal, 1e-8);
192 }
193 double AngleSum = 0.;
194 if (AllNormal) {
195 // check by angle sum whether points go round are cover just one half
196 IndexList_t::const_iterator indexiter = _indices.begin();
197 Vector CurrentVector = _positions[*indexiter++];
198 for(; indexiter != _indices.end(); ++indexiter) {
199 AngleSum += CurrentVector.Angle(_positions[*indexiter]);
200 CurrentVector = _positions[*indexiter];
201 }
202 }
203 if (AngleSum - M_PI > std::numeric_limits<double>::epsilon()*1e4) {
204// Vector candidate;
205// double oldSKP = -1.;
206// for (IndexList_t::const_iterator iter = _indices.begin();
207// iter != _indices.end(); ++iter) {
208// const double newSKP = _positions[*iter].ScalarProduct(center);
209// if (newSKP > oldSKP) {
210// candidate = _positions[*iter];
211// oldSKP = newSKP;
212// }
213// }
214 // non-uniqueness: all positions on great circle, normal to given line are valid
215 // so, we just pick one because returning a unique point is topmost priority
216// Vector normal;
217// normal.GetOneNormalVector(candidate);
218// Vector othernormal = candidate;
219// othernormal.VectorProduct(normal);
220// // now both normal and othernormal describe the plane containing the great circle
221// Plane greatcircle(normal, zeroVec, othernormal);
222 // check which axis is contained and pick the one not
223// if (greatcircle.isContained(center)) {
224// center = Vector(0.,1.,0.);
225// if (greatcircle.isContained(center))
226// center = Vector(0.,0.,1.);
227// // now we are done cause a plane cannot contain all three axis vectors
228// }
229// center = greatcircle.getClosestPoint(candidate);
230// center = greatcircle.getNormal();
231 center = Normal;
232 // assure length of 1
233 center.Normalize();
234
235 return center;
236 }
237 }
238 }
239
240 // start with geographic midpoint
241 center = calculateGeographicMidpoint(_positions, _indices);
242 if (!center.IsZero()) {
243 center.Normalize();
244 LOG(5, "DEBUG: Starting with geographical midpoint of " << _positions << " under indices "
245 << _indices << " is " << center);
246 } else {
247 // any point is good actually
248 center = _positions[0];
249 LOG(5, "DEBUG: Starting with first position " << center);
250 }
251
252 // calculate initial MinimumDistance
253 double MinimumDistance = calculateMinimumDistance(center, _positions, _indices);
254 LOG(6, "DEBUG: MinimumDistance to this center is " << MinimumDistance);
255
256 // check all present points whether they may serve as a better center
257 for (SphericalPointDistribution::IndexList_t::const_iterator iter = _indices.begin();
258 iter != _indices.end(); ++iter) {
259 const Vector &centerCandidate = _positions[*iter];
260 const double candidateDistance = calculateMinimumDistance(centerCandidate, _positions, _indices);
261 if (candidateDistance < MinimumDistance) {
262 MinimumDistance = candidateDistance;
263 center = centerCandidate;
264 LOG(6, "DEBUG: new MinimumDistance to current test point " << MinimumDistance
265 << " is " << center);
266 }
267 }
268 LOG(6, "DEBUG: new MinimumDistance to center " << center << " is " << MinimumDistance);
269
270 // now iterate over TestDistance
271 double TestDistance = Vector(1.,0.,0.).Angle(Vector(0.,1.,0.));
272// LOG(6, "DEBUG: initial TestDistance is " << TestDistance);
273
274 const double threshold = sqrt(std::numeric_limits<double>::epsilon());
275 // check each of eight test points at N, NE, E, SE, S, SW, W, NW
276 typedef std::vector<double> angles_t;
277 angles_t testangles;
278 testangles += 0./180.*M_PI, 45./180.*M_PI, 90./180.*M_PI, 135./180.*M_PI,
279 180./180.*M_PI, 225./180.*M_PI, 270./180.*M_PI, 315./180.*M_PI;
280 while (TestDistance > threshold) {
281 Vector OneNormal;
282 OneNormal.GetOneNormalVector(center);
283 Line RotationAxis(zeroVec, OneNormal);
284 Vector North = RotationAxis.rotateVector(center,TestDistance);
285 Line CompassRose(zeroVec, center);
286 bool updatedflag = false;
287 for (angles_t::const_iterator angleiter = testangles.begin();
288 angleiter != testangles.end(); ++angleiter) {
289 Vector centerCandidate = CompassRose.rotateVector(North, *angleiter);
290// centerCandidate.Normalize();
291 const double candidateDistance = calculateMinimumDistance(centerCandidate, _positions, _indices);
292 if (candidateDistance < MinimumDistance) {
293 MinimumDistance = candidateDistance;
294 center = centerCandidate;
295 updatedflag = true;
296 LOG(7, "DEBUG: new MinimumDistance to test point at " << *angleiter/M_PI*180.
297 << "° is " << MinimumDistance);
298 }
299 }
300
301 // if no new point, decrease TestDistance
302 if (!updatedflag) {
303 TestDistance *= 0.5;
304// LOG(6, "DEBUG: TestDistance is now " << TestDistance);
305 }
306 }
307 LOG(4, "DEBUG: Final MinimumDistance to center " << center << " is " << MinimumDistance);
308
309 return center;
310}
311
312Vector calculateCenterOfMinimumDistance(
313 const SphericalPointDistribution::PolygonWithIndices &_points)
314{
315 return SphericalPointDistribution::calculateCenterOfMinimumDistance(_points.polygon, _points.indices);
316}
317
318/** Calculate the center of a given set of points in \a _positions but only
319 * for those indicated by \a _indices.
320 *
321 */
322inline
323Vector calculateCenterOfGravity(
324 const SphericalPointDistribution::VectorArray_t &_positions,
325 const SphericalPointDistribution::IndexList_t &_indices)
326{
327 Vector Center;
328 Center.Zero();
329 for (SphericalPointDistribution::IndexList_t::const_iterator iter = _indices.begin();
330 iter != _indices.end(); ++iter)
331 Center += _positions[*iter];
332 if (!_indices.empty())
333 Center *= 1./(double)_indices.size();
334
335 return Center;
336}
337
338/** Calculate the center of a given set of points in \a _positions but only
339 * for those indicated by \a _indices.
340 *
341 */
342inline
343Vector calculateCenter(
344 const SphericalPointDistribution::VectorArray_t &_positions,
345 const SphericalPointDistribution::IndexList_t &_indices)
346{
347// Vector Center;
348// Center.Zero();
349// for (SphericalPointDistribution::IndexList_t::const_iterator iter = _indices.begin();
350// iter != _indices.end(); ++iter)
351// Center += _positions[*iter];
352// if (!_indices.empty())
353// Center *= 1./(double)_indices.size();
354//
355// return Center;
356 return SphericalPointDistribution::calculateCenterOfMinimumDistance(_positions, _indices);
357}
358
359/** Calculate the center of a given set of points in \a _positions but only
360 * for those indicated by \a _indices.
361 *
362 */
363inline
364Vector calculateCenter(
365 const SphericalPointDistribution::PolygonWithIndices &_polygon)
366{
367 return calculateCenter(_polygon.polygon, _polygon.indices);
368}
369
370inline
371DistanceArray_t calculatePairwiseDistances(
372 const SphericalPointDistribution::VectorArray_t &_points,
373 const SphericalPointDistribution::IndexTupleList_t &_indices
374 )
375{
376 DistanceArray_t result;
377 for (SphericalPointDistribution::IndexTupleList_t::const_iterator firstiter = _indices.begin();
378 firstiter != _indices.end(); ++firstiter) {
379
380 // calculate first center from possible tuple of indices
381 Vector FirstCenter;
382 ASSERT(!firstiter->empty(), "calculatePairwiseDistances() - there is an empty tuple.");
383 if (firstiter->size() == 1) {
384 FirstCenter = _points[*firstiter->begin()];
385 } else {
386 FirstCenter = calculateCenter( _points, *firstiter);
387 if (!FirstCenter.IsZero())
388 FirstCenter.Normalize();
389 }
390
391 for (SphericalPointDistribution::IndexTupleList_t::const_iterator seconditer = firstiter;
392 seconditer != _indices.end(); ++seconditer) {
393 if (firstiter == seconditer)
394 continue;
395
396 // calculate second center from possible tuple of indices
397 Vector SecondCenter;
398 ASSERT(!seconditer->empty(), "calculatePairwiseDistances() - there is an empty tuple.");
399 if (seconditer->size() == 1) {
400 SecondCenter = _points[*seconditer->begin()];
401 } else {
402 SecondCenter = calculateCenter( _points, *seconditer);
403 if (!SecondCenter.IsZero())
404 SecondCenter.Normalize();
405 }
406
407 // calculate distance between both centers
408 double distance = 2.; // greatest distance on surface of sphere with radius 1.
409 if ((!FirstCenter.IsZero()) && (!SecondCenter.IsZero()))
410 distance = (FirstCenter - SecondCenter).NormSquared();
411 result.push_back(distance);
412 }
413 }
414 return result;
415}
416
417/** Decides by an orthonormal third vector whether the sign of the rotation
418 * angle should be negative or positive.
419 *
420 * \return -1 or 1
421 */
422inline
423double determineSignOfRotation(
424 const Vector &_oldPosition,
425 const Vector &_newPosition,
426 const Vector &_RotationAxis
427 )
428{
429 Vector dreiBein(_oldPosition);
430 dreiBein.VectorProduct(_RotationAxis);
431 ASSERT( !dreiBein.IsZero(), "determineSignOfRotation() - dreiBein is zero.");
432 dreiBein.Normalize();
433 const double sign =
434 (dreiBein.ScalarProduct(_newPosition) < 0.) ? -1. : +1.;
435 LOG(6, "DEBUG: oldCenter on plane is " << _oldPosition
436 << ", newCenter on plane is " << _newPosition
437 << ", and dreiBein is " << dreiBein);
438 return sign;
439}
440
441/** Convenience function to recalculate old and new center for determining plane
442 * rotation.
443 */
444inline
445void calculateOldAndNewCenters(
446 Vector &_oldCenter,
447 Vector &_newCenter,
448 const SphericalPointDistribution::PolygonWithIndices &_referencepositions,
449 const SphericalPointDistribution::PolygonWithIndices &_currentpositions)
450{
451 _oldCenter = calculateCenter(_referencepositions.polygon, _referencepositions.indices);
452 // C++11 defines a copy_n function ...
453 _newCenter = calculateCenter( _currentpositions.polygon, _currentpositions.indices);
454}
455/** Returns squared L2 error of the given \a _Matching.
456 *
457 * We compare the pair-wise distances of each associated matching
458 * and check whether these distances each match between \a _old and
459 * \a _new.
460 *
461 * \param _old first set of points (fewer or equal to \a _new)
462 * \param _new second set of points
463 * \param _Matching matching between the two sets
464 * \return pair with L1 and squared L2 error
465 */
466std::pair<double, double> SphericalPointDistribution::calculateErrorOfMatching(
467 const VectorArray_t &_old,
468 const VectorArray_t &_new,
469 const IndexTupleList_t &_Matching)
470{
471 std::pair<double, double> errors( std::make_pair( 0., 0. ) );
472
473 // the error is the deviation from the mean angle
474 std::vector<double> distances;
475 double mean = 0.;
476 for (IndexTupleList_t::const_iterator matchingiter = _Matching.begin();
477 matchingiter != _Matching.end(); ++matchingiter) {
478 // calculate distance on surface as rotation angle
479 const Vector newcenter = calculateCenter(_new, *matchingiter);
480 const Vector &oldcenter = _old[std::distance(_Matching.begin(), matchingiter)];
481 Vector axis = newcenter;
482 axis.VectorProduct(oldcenter);
483 axis.Normalize();
484 const double distance = newcenter.Angle(oldcenter);
485 distances.push_back(distance);
486 mean += distance;
487 }
488 if (!_Matching.empty())
489 mean *= 1./(double)_Matching.size();
490 LOG(5, "DEBUG: Mean distance is " << mean << " for " << _Matching.size() << " points.");
491
492 // analyse errors
493 for (std::vector<double>::const_iterator iter = distances.begin();
494 iter != distances.end(); ++iter) {
495 const double difference = fabs(*iter - mean);
496 if (errors.first < difference) {
497 errors.first = difference;
498 }
499 errors.second += difference*difference;
500
501 }
502 errors.second = sqrt(errors.second);
503
504// if (!_Matching.empty()) {
505// // there is at least one distance, we've checked that before
506// errors.second *= 1./(double)_Matching.size();
507// }
508
509// if (_Matching.size() > 1) {
510// LOG(5, "INFO: Matching is " << _Matching);
511//
512// // calculate all pair-wise distances
513// IndexTupleList_t keys(_old.size(), IndexList_t() );
514// std::generate (keys.begin(), keys.end(), UniqueNumberList);
515//
516// const DistanceArray_t firstdistances = calculatePairwiseDistances(_old, keys);
517// const DistanceArray_t seconddistances = calculatePairwiseDistances(_new, _Matching);
518//
519// ASSERT( firstdistances.size() == seconddistances.size(),
520// "calculateL2ErrorOfMatching() - mismatch in pair-wise distance array sizes.");
521// DistanceArray_t::const_iterator firstiter = firstdistances.begin();
522// DistanceArray_t::const_iterator seconditer = seconddistances.begin();
523// for (;(firstiter != firstdistances.end()) && (seconditer != seconddistances.end());
524// ++firstiter, ++seconditer) {
525// const double gap = fabs(*firstiter - *seconditer);
526// // L1 error
527// if (errors.first < gap)
528// errors.first = gap;
529// // L2 error
530// errors.second += gap*gap;
531// }
532// // there is at least one distance, we've checked that before
533// errors.second *= 1./(double)firstdistances.size();
534// } else {
535// // check whether we have any zero centers: Combining points on new sphere may result
536// // in zero centers
537// for (SphericalPointDistribution::IndexTupleList_t::const_iterator iter = _Matching.begin();
538// iter != _Matching.end(); ++iter) {
539// if ((iter->size() != 1) && (calculateCenter( _new, *iter).IsZero())) {
540// errors.first = 2.;
541// errors.second = 2.;
542// }
543// }
544// }
545 LOG(4, "INFO: Resulting errors for matching (L1, L2): "
546 << errors.first << "," << errors.second << ".");
547
548 return errors;
549}
550
551SphericalPointDistribution::Polygon_t SphericalPointDistribution::removeMatchingPoints(
552 const PolygonWithIndices &_points
553 )
554{
555 SphericalPointDistribution::Polygon_t remainingpoints;
556 IndexArray_t indices(_points.indices.begin(), _points.indices.end());
557 std::sort(indices.begin(), indices.end());
558 LOG(4, "DEBUG: sorted matching is " << indices);
559 IndexArray_t remainingindices(_points.polygon.size(), -1);
560 std::generate(remainingindices.begin(), remainingindices.end(), UniqueNumber);
561 IndexArray_t::iterator remainiter = std::set_difference(
562 remainingindices.begin(), remainingindices.end(),
563 indices.begin(), indices.end(),
564 remainingindices.begin());
565 remainingindices.erase(remainiter, remainingindices.end());
566 LOG(4, "DEBUG: remaining indices are " << remainingindices);
567 for (IndexArray_t::const_iterator iter = remainingindices.begin();
568 iter != remainingindices.end(); ++iter) {
569 remainingpoints.push_back(_points.polygon[*iter]);
570 }
571
572 return remainingpoints;
573}
574
575/** Recursive function to go through all possible matchings.
576 *
577 * \param _MCS structure holding global information to the recursion
578 * \param _matching current matching being build up
579 * \param _indices contains still available indices
580 * \param _remainingweights current weights to fill (each weight a place)
581 * \param _remainiter iterator over the weights, indicating the current position we match
582 * \param _matchingsize
583 */
584void SphericalPointDistribution::recurseMatchings(
585 MatchingControlStructure &_MCS,
586 IndexTupleList_t &_matching,
587 IndexList_t _indices,
588 WeightsArray_t &_remainingweights,
589 WeightsArray_t::iterator _remainiter,
590 const unsigned int _matchingsize
591 )
592{
593 LOG(5, "DEBUG: Recursing with current matching " << _matching
594 << ", remaining indices " << _indices
595 << ", and remaining weights " << _matchingsize);
596 if (!_MCS.foundflag) {
597 LOG(5, "DEBUG: Current matching has size " << _matching.size() << ", places left " << _matchingsize);
598 if (_matchingsize > 0) {
599 // go through all indices
600 for (IndexList_t::iterator iter = _indices.begin();
601 (iter != _indices.end()) && (!_MCS.foundflag);) {
602
603 // check whether we can stay in the current bin or have to move on to next one
604 if (*_remainiter == 0) {
605 // we need to move on
606 if (_remainiter != _remainingweights.end()) {
607 ++_remainiter;
608 } else {
609 // as we check _matchingsize > 0 this should be impossible
610 ASSERT( 0, "recurseMatchings() - we must not come to this position.");
611 }
612 }
613
614 // advance in matching to current bin to fill in
615 const size_t OldIndex = std::distance(_remainingweights.begin(), _remainiter);
616 while (_matching.size() <= OldIndex) { // add empty lists if new bin is opened
617 LOG(6, "DEBUG: Extending _matching.");
618 _matching.push_back( IndexList_t() );
619 }
620 IndexTupleList_t::iterator filliniter = _matching.begin();
621 std::advance(filliniter, OldIndex);
622
623 // check whether connection between bins' indices and candidate is satisfied
624 if (!_MCS.adjacency.empty()) {
625 adjacency_t::const_iterator finder = _MCS.adjacency.find(*iter);
626 ASSERT( finder != _MCS.adjacency.end(),
627 "recurseMatchings() - "+toString(*iter)+" is not in adjacency list.");
628 if ((!filliniter->empty())
629 && (finder->second.find(*filliniter->begin()) == finder->second.end())) {
630 LOG(5, "DEBUG; Skipping index " << *iter
631 << " as is not connected to current set." << *filliniter << ".");
632 ++iter; // note that index-loop does not contain incrementor
633 continue;
634 }
635 }
636
637 // add index to matching
638 filliniter->push_back(*iter);
639 --(*_remainiter);
640 LOG(6, "DEBUG: Adding " << *iter << " to matching at " << OldIndex << ".");
641 // remove index but keep iterator to position (is the next to erase element)
642 IndexList_t::iterator backupiter = _indices.erase(iter);
643 // recurse with decreased _matchingsize
644 recurseMatchings(_MCS, _matching, _indices, _remainingweights, _remainiter, _matchingsize-1);
645 // re-add chosen index and reset index to new position
646 _indices.insert(backupiter, filliniter->back());
647 iter = backupiter;
648 // remove index from _matching to make space for the next one
649 filliniter->pop_back();
650 ++(*_remainiter);
651 }
652 // gone through all indices then exit recursion
653 if (_matching.empty())
654 _MCS.foundflag = true;
655 } else {
656 LOG(4, "INFO: Found matching " << _matching);
657 // calculate errors
658 std::pair<double, double> errors = calculateErrorOfMatching(
659 _MCS.oldpoints, _MCS.newpoints, _matching);
660 if (errors.first < L1THRESHOLD) {
661 _MCS.bestmatching = _matching;
662 _MCS.foundflag = true;
663 } else if (_MCS.bestL2 > errors.second) {
664 _MCS.bestmatching = _matching;
665 _MCS.bestL2 = errors.second;
666 }
667 }
668 }
669}
670
671SphericalPointDistribution::MatchingControlStructure::MatchingControlStructure(
672 const adjacency_t &_adjacency,
673 const VectorArray_t &_oldpoints,
674 const VectorArray_t &_newpoints,
675 const WeightsArray_t &_weights
676 ) :
677 foundflag(false),
678 bestL2(std::numeric_limits<double>::max()),
679 adjacency(_adjacency),
680 oldpoints(_oldpoints),
681 newpoints(_newpoints),
682 weights(_weights)
683{}
684
685/** Finds combinatorially the best matching between points in \a _polygon
686 * and \a _newpolygon.
687 *
688 * We find the matching with the smallest L2 error, where we break when we stumble
689 * upon a matching with zero error.
690 *
691 * As points in \a _polygon may be have a weight greater 1 we have to match it to
692 * multiple points in \a _newpolygon. Eventually, these multiple points are combined
693 * for their center of weight, which is the only thing follow-up function see of
694 * these multiple points. Hence, we actually modify \a _newpolygon in the process
695 * such that the returned IndexList_t indicates a bijective mapping in the end.
696 *
697 * \sa recurseMatchings() for going through all matchings
698 *
699 * \param _polygon here, we have indices 0,1,2,...
700 * \param _newpolygon and here we need to find the correct indices
701 * \return control structure containing the matching and more
702 */
703SphericalPointDistribution::MatchingControlStructure
704SphericalPointDistribution::findBestMatching(
705 const WeightedPolygon_t &_polygon
706 )
707{
708 // transform lists into arrays
709 VectorArray_t oldpoints;
710 VectorArray_t newpoints;
711 WeightsArray_t weights;
712 for (WeightedPolygon_t::const_iterator iter = _polygon.begin();
713 iter != _polygon.end(); ++iter) {
714 oldpoints.push_back(iter->first);
715 weights.push_back(iter->second);
716 }
717 newpoints.insert(newpoints.begin(), points.begin(), points.end() );
718 MatchingControlStructure MCS(adjacency, oldpoints, newpoints, weights);
719
720 // search for bestmatching combinatorially
721 {
722 // translate polygon into vector to enable index addressing
723 IndexList_t indices(points.size());
724 std::generate(indices.begin(), indices.end(), UniqueNumber);
725 IndexTupleList_t matching;
726
727 // walk through all matchings
728 WeightsArray_t remainingweights = MCS.weights;
729 unsigned int placesleft = std::accumulate(remainingweights.begin(), remainingweights.end(), 0);
730 recurseMatchings(MCS, matching, indices, remainingweights, remainingweights.begin(), placesleft);
731 }
732 if (MCS.foundflag)
733 LOG(3, "Found a best matching beneath L1 threshold of " << L1THRESHOLD);
734 else {
735 if (MCS.bestL2 < warn_amplitude)
736 LOG(3, "Picking matching is " << MCS.bestmatching << " with best L2 error of "
737 << MCS.bestL2);
738 else if (MCS.bestL2 < L2THRESHOLD)
739 ELOG(2, "Picking matching is " << MCS.bestmatching
740 << " with rather large L2 error of " << MCS.bestL2);
741 else
742 ASSERT(0, "findBestMatching() - matching "+toString(MCS.bestmatching)
743 +" has L2 error of "+toString(MCS.bestL2)+" that is too large.");
744 }
745
746 return MCS;
747}
748
749SphericalPointDistribution::IndexList_t SphericalPointDistribution::joinPoints(
750 Polygon_t &_newpolygon,
751 const VectorArray_t &_newpoints,
752 const IndexTupleList_t &_bestmatching
753 )
754{
755 // combine all multiple points
756 IndexList_t IndexList;
757 IndexArray_t removalpoints;
758 unsigned int UniqueIndex = _newpolygon.size(); // all indices up to size are used right now
759 VectorArray_t newCenters;
760 newCenters.reserve(_bestmatching.size());
761 for (IndexTupleList_t::const_iterator tupleiter = _bestmatching.begin();
762 tupleiter != _bestmatching.end(); ++tupleiter) {
763 ASSERT (tupleiter->size() > 0,
764 "findBestMatching() - encountered tuple in bestmatching with size 0.");
765 if (tupleiter->size() == 1) {
766 // add point and index
767 IndexList.push_back(*tupleiter->begin());
768 } else {
769 // combine into weighted and normalized center
770 Vector Center = calculateCenter(_newpoints, *tupleiter);
771 Center.Normalize();
772 _newpolygon.push_back(Center);
773 LOG(5, "DEBUG: Combining " << tupleiter->size() << " points to weighted center "
774 << Center << " with new index " << UniqueIndex);
775 // mark for removal
776 removalpoints.insert(removalpoints.end(), tupleiter->begin(), tupleiter->end());
777 // add new index
778 IndexList.push_back(UniqueIndex++);
779 }
780 }
781 // IndexList is now our new bestmatching (that is bijective)
782 LOG(4, "DEBUG: Our new bijective IndexList reads as " << IndexList);
783
784 // modifying _newpolygon: remove all points in removalpoints, add those in newCenters
785 Polygon_t allnewpoints = _newpolygon;
786 {
787 _newpolygon.clear();
788 std::sort(removalpoints.begin(), removalpoints.end());
789 size_t i = 0;
790 IndexArray_t::const_iterator removeiter = removalpoints.begin();
791 for (Polygon_t::iterator iter = allnewpoints.begin();
792 iter != allnewpoints.end(); ++iter, ++i) {
793 if ((removeiter != removalpoints.end()) && (i == *removeiter)) {
794 // don't add, go to next remove index
795 ++removeiter;
796 } else {
797 // otherwise add points
798 _newpolygon.push_back(*iter);
799 }
800 }
801 }
802 LOG(4, "DEBUG: The polygon with recentered points removed is " << _newpolygon);
803
804 // map IndexList to new shrinked _newpolygon
805 typedef std::set<unsigned int> IndexSet_t;
806 IndexSet_t SortedIndexList(IndexList.begin(), IndexList.end());
807 IndexList.clear();
808 {
809 size_t offset = 0;
810 IndexSet_t::const_iterator listiter = SortedIndexList.begin();
811 IndexArray_t::const_iterator removeiter = removalpoints.begin();
812 for (size_t i = 0; i < allnewpoints.size(); ++i) {
813 if ((removeiter != removalpoints.end()) && (i == *removeiter)) {
814 ++offset;
815 ++removeiter;
816 } else if ((listiter != SortedIndexList.end()) && (i == *listiter)) {
817 IndexList.push_back(*listiter - offset);
818 ++listiter;
819 }
820 }
821 }
822 LOG(4, "DEBUG: Our new bijective IndexList corrected for removed points reads as "
823 << IndexList);
824
825 return IndexList;
826}
827
828SphericalPointDistribution::Rotation_t SphericalPointDistribution::findPlaneAligningRotation(
829 const PolygonWithIndices &_referencepositions,
830 const PolygonWithIndices &_currentpositions
831 )
832{
833 bool dontcheck = false;
834 // initialize to no rotation
835 Rotation_t Rotation;
836 Rotation.first.Zero();
837 Rotation.first[0] = 1.;
838 Rotation.second = 0.;
839
840 // calculate center of triangle/line/point consisting of first points of matching
841 Vector oldCenter;
842 Vector newCenter;
843 calculateOldAndNewCenters(
844 oldCenter, newCenter,
845 _referencepositions, _currentpositions);
846
847 ASSERT( !oldCenter.IsZero() && !newCenter.IsZero(),
848 "findPlaneAligningRotation() - either old "+toString(oldCenter)
849 +" or new center "+toString(newCenter)+" are zero.");
850 LOG(4, "DEBUG: oldCenter is " << oldCenter << ", newCenter is " << newCenter);
851 if (!oldCenter.IsEqualTo(newCenter)) {
852 // calculate rotation axis and angle
853 Rotation.first = oldCenter;
854 Rotation.first.VectorProduct(newCenter);
855 Rotation.first.Normalize();
856 // construct reference vector to determine direction of rotation
857 const double sign = determineSignOfRotation(newCenter, oldCenter, Rotation.first);
858 Rotation.second = sign * oldCenter.Angle(newCenter);
859 } else {
860 // no rotation required anymore
861 }
862
863#ifndef NDEBUG
864 // check: rotation brings newCenter onto oldCenter position
865 if (!dontcheck) {
866 Line Axis(zeroVec, Rotation.first);
867 Vector test = Axis.rotateVector(newCenter, Rotation.second);
868 LOG(4, "CHECK: rotated newCenter is " << test
869 << ", oldCenter is " << oldCenter);
870 ASSERT( (test - oldCenter).NormSquared() < std::numeric_limits<double>::epsilon()*1e4,
871 "matchSphericalPointDistributions() - rotation does not work as expected by "
872 +toString((test - oldCenter).NormSquared())+".");
873 }
874#endif
875
876 return Rotation;
877}
878
879SphericalPointDistribution::Rotation_t SphericalPointDistribution::findPointAligningRotation(
880 const PolygonWithIndices &remainingold,
881 const PolygonWithIndices &remainingnew)
882{
883 // initialize rotation to zero
884 Rotation_t Rotation;
885 Rotation.first.Zero();
886 Rotation.first[0] = 1.;
887 Rotation.second = 0.;
888
889 // recalculate center
890 Vector oldCenter;
891 Vector newCenter;
892 calculateOldAndNewCenters(
893 oldCenter, newCenter,
894 remainingold, remainingnew);
895
896 // we rotate at oldCenter and around the radial direction, which is again given
897 // by oldCenter.
898 Rotation.first = oldCenter;
899 Rotation.first.Normalize(); // note weighted sum of normalized weight is not normalized
900
901 // calculate center of the rotational plane
902 newCenter = calculateCenterOfGravity(remainingnew.polygon, remainingnew.indices);
903 oldCenter = calculateCenterOfGravity(remainingold.polygon, remainingold.indices);
904 LOG(6, "DEBUG: Using oldCenter " << oldCenter << " as rotation center and "
905 << Rotation.first << " as axis.");
906
907 LOG(6, "DEBUG: old indices are " << remainingold.indices
908 << ", new indices are " << remainingnew.indices);
909 IndexList_t::const_iterator newiter = remainingnew.indices.begin();
910 IndexList_t::const_iterator olditer = remainingold.indices.begin();
911 for (;
912 (newiter != remainingnew.indices.end()) && (olditer != remainingold.indices.end());
913 ++newiter,++olditer) {
914 Vector newPosition = remainingnew.polygon[*newiter];
915 Vector oldPosition = remainingold.polygon[*olditer];
916 LOG(6, "DEBUG: oldPosition is " << oldPosition << " (length: "
917 << oldPosition.Norm() << ") and newPosition is " << newPosition << " length(: "
918 << newPosition.Norm() << ")");
919
920 if (!oldPosition.IsEqualTo(newPosition)) {
921 oldPosition -= oldCenter;
922 newPosition -= newCenter;
923 oldPosition = (oldPosition - oldPosition.Projection(Rotation.first));
924 newPosition = (newPosition - newPosition.Projection(Rotation.first));
925 LOG(6, "DEBUG: Positions after projection are " << oldPosition << " and " << newPosition);
926
927 // construct reference vector to determine direction of rotation
928 const double sign = determineSignOfRotation(newPosition, oldPosition, Rotation.first);
929 LOG(6, "DEBUG: Determining angle between " << oldPosition << " and " << newPosition);
930 const double angle = sign * newPosition.Angle(oldPosition);
931 LOG(6, "DEBUG: Adding " << angle << " to weighted rotation sum.");
932 Rotation.second += angle;
933 } else {
934 LOG(6, "DEBUG: oldPosition and newPosition are equivalent, hence no orientating rotation.");
935 }
936 }
937 Rotation.second *= 1./(double)remainingnew.indices.size();
938
939 return Rotation;
940}
941
942void SphericalPointDistribution::initSelf(const int _NumberOfPoints)
943{
944 switch (_NumberOfPoints)
945 {
946 case 0:
947 points = get<0>();
948 adjacency = getConnections<0>();
949 break;
950 case 1:
951 points = get<1>();
952 adjacency = getConnections<1>();
953 break;
954 case 2:
955 points = get<2>();
956 adjacency = getConnections<2>();
957 break;
958 case 3:
959 points = get<3>();
960 adjacency = getConnections<3>();
961 break;
962 case 4:
963 points = get<4>();
964 adjacency = getConnections<4>();
965 break;
966 case 5:
967 points = get<5>();
968 adjacency = getConnections<5>();
969 break;
970 case 6:
971 points = get<6>();
972 adjacency = getConnections<6>();
973 break;
974 case 7:
975 points = get<7>();
976 adjacency = getConnections<7>();
977 break;
978 case 8:
979 points = get<8>();
980 adjacency = getConnections<8>();
981 break;
982 case 9:
983 points = get<9>();
984 adjacency = getConnections<9>();
985 break;
986 case 10:
987 points = get<10>();
988 adjacency = getConnections<10>();
989 break;
990 case 11:
991 points = get<11>();
992 adjacency = getConnections<11>();
993 break;
994 case 12:
995 points = get<12>();
996 adjacency = getConnections<12>();
997 break;
998 case 14:
999 points = get<14>();
1000 adjacency = getConnections<14>();
1001 break;
1002 default:
1003 ASSERT(0, "SphericalPointDistribution::initSelf() - cannot deal with the case "
1004 +toString(_NumberOfPoints)+".");
1005 }
1006 LOG(3, "DEBUG: Ideal polygon is " << points);
1007}
1008
1009SphericalPointDistribution::Polygon_t
1010SphericalPointDistribution::getRemainingPoints(
1011 const WeightedPolygon_t &_polygon,
1012 const int _N)
1013{
1014 SphericalPointDistribution::Polygon_t remainingpoints;
1015
1016 // initialze to given number of points
1017 initSelf(_N);
1018 LOG(2, "INFO: Matching old polygon " << _polygon
1019 << " with new polygon " << points);
1020
1021 // check whether any points will remain vacant
1022 int RemainingPoints = _N;
1023 for (WeightedPolygon_t::const_iterator iter = _polygon.begin();
1024 iter != _polygon.end(); ++iter)
1025 RemainingPoints -= iter->second;
1026 if (RemainingPoints == 0)
1027 return remainingpoints;
1028
1029 if (_N > 0) {
1030 // combine multiple points and create simple IndexList from IndexTupleList
1031 MatchingControlStructure MCS = findBestMatching(_polygon);
1032 IndexList_t bestmatching = joinPoints(points, MCS.newpoints, MCS.bestmatching);
1033 LOG(2, "INFO: Best matching is " << bestmatching);
1034
1035 const size_t NumberIds = std::min(bestmatching.size(), (size_t)3);
1036 // create old set
1037 PolygonWithIndices oldSet;
1038 oldSet.indices.resize(NumberIds, -1);
1039 std::generate(oldSet.indices.begin(), oldSet.indices.end(), UniqueNumber);
1040 for (WeightedPolygon_t::const_iterator iter = _polygon.begin();
1041 iter != _polygon.end(); ++iter)
1042 oldSet.polygon.push_back(iter->first);
1043
1044 // _newpolygon has changed, so now convert to array with matched indices
1045 PolygonWithIndices newSet;
1046 SphericalPointDistribution::IndexList_t::const_iterator beginiter = bestmatching.begin();
1047 SphericalPointDistribution::IndexList_t::const_iterator enditer = bestmatching.begin();
1048 std::advance(enditer, NumberIds);
1049 newSet.indices.resize(NumberIds, -1);
1050 std::copy(beginiter, enditer, newSet.indices.begin());
1051 std::copy(points.begin(),points.end(), std::back_inserter(newSet.polygon));
1052
1053 // determine rotation angles to align the two point distributions with
1054 // respect to bestmatching:
1055 // we use the center between the three first matching points
1056 /// the first rotation brings these two centers to coincide
1057 PolygonWithIndices rotatednewSet = newSet;
1058 {
1059 Rotation_t Rotation = findPlaneAligningRotation(oldSet, newSet);
1060 LOG(5, "DEBUG: Rotating coordinate system by " << Rotation.second
1061 << " around axis " << Rotation.first);
1062 Line Axis(zeroVec, Rotation.first);
1063
1064 // apply rotation angle to bring newCenter to oldCenter
1065 for (VectorArray_t::iterator iter = rotatednewSet.polygon.begin();
1066 iter != rotatednewSet.polygon.end(); ++iter) {
1067 Vector &current = *iter;
1068 LOG(6, "DEBUG: Original point is " << current);
1069 current = Axis.rotateVector(current, Rotation.second);
1070 LOG(6, "DEBUG: Rotated point is " << current);
1071 }
1072
1073#ifndef NDEBUG
1074 // check: rotated "newCenter" should now equal oldCenter
1075 // we don't check in case of two points as these lie on a great circle
1076 // and the center cannot stably be recalculated. We may reactivate this
1077 // when we calculate centers only once
1078 if (oldSet.indices.size() > 2) {
1079 Vector oldCenter;
1080 Vector rotatednewCenter;
1081 calculateOldAndNewCenters(
1082 oldCenter, rotatednewCenter,
1083 oldSet, rotatednewSet);
1084 oldCenter.Normalize();
1085 rotatednewCenter.Normalize();
1086 // check whether centers are anti-parallel (factor -1)
1087 // then we have the "non-unique poles" situation: points lie on great circle
1088 // and both poles are valid solution
1089 if (fabs(oldCenter.ScalarProduct(rotatednewCenter) + 1.)
1090 < std::numeric_limits<double>::epsilon()*1e4)
1091 rotatednewCenter *= -1.;
1092 LOG(4, "CHECK: rotatednewCenter is " << rotatednewCenter
1093 << ", oldCenter is " << oldCenter);
1094 const double difference = (rotatednewCenter - oldCenter).NormSquared();
1095 ASSERT( difference < std::numeric_limits<double>::epsilon()*1e4,
1096 "matchSphericalPointDistributions() - rotation does not work as expected by "
1097 +toString(difference)+".");
1098 }
1099#endif
1100 }
1101 /// the second (orientation) rotation aligns the planes such that the
1102 /// points themselves coincide
1103 if (bestmatching.size() > 1) {
1104 Rotation_t Rotation = findPointAligningRotation(oldSet, rotatednewSet);
1105
1106 // construct RotationAxis and two points on its plane, defining the angle
1107 Rotation.first.Normalize();
1108 const Line RotationAxis(zeroVec, Rotation.first);
1109
1110 LOG(5, "DEBUG: Rotating around self is " << Rotation.second
1111 << " around axis " << RotationAxis);
1112
1113#ifndef NDEBUG
1114 // check: first bestmatching in rotated_newpolygon and remainingnew
1115 // should now equal
1116 {
1117 const IndexList_t::const_iterator iter = bestmatching.begin();
1118
1119 // check whether both old and newPosition are at same distance to oldCenter
1120 Vector oldCenter = calculateCenter(oldSet);
1121 const double distance = fabs(
1122 (oldSet.polygon[0] - oldCenter).NormSquared()
1123 - (rotatednewSet.polygon[*iter] - oldCenter).NormSquared()
1124 );
1125 LOG(4, "CHECK: Squared distance between oldPosition and newPosition "
1126 << " with respect to oldCenter " << oldCenter << " is " << distance);
1127// ASSERT( distance < warn_amplitude,
1128// "matchSphericalPointDistributions() - old and newPosition's squared distance to oldCenter differs by "
1129// +toString(distance));
1130
1131 Vector rotatednew = RotationAxis.rotateVector(
1132 rotatednewSet.polygon[*iter],
1133 Rotation.second);
1134 LOG(4, "CHECK: rotated first new bestmatching is " << rotatednew
1135 << " while old was " << oldSet.polygon[0]);
1136 const double difference = (rotatednew - oldSet.polygon[0]).NormSquared();
1137 ASSERT( difference < distance+warn_amplitude,
1138 "matchSphericalPointDistributions() - orientation rotation ends up off by "
1139 +toString(difference)+", more than "
1140 +toString(distance+warn_amplitude)+".");
1141 }
1142#endif
1143
1144 for (VectorArray_t::iterator iter = rotatednewSet.polygon.begin();
1145 iter != rotatednewSet.polygon.end(); ++iter) {
1146 Vector &current = *iter;
1147 LOG(6, "DEBUG: Original point is " << current);
1148 current = RotationAxis.rotateVector(current, Rotation.second);
1149 LOG(6, "DEBUG: Rotated point is " << current);
1150 }
1151 }
1152
1153 // remove all points in matching and return remaining ones
1154 SphericalPointDistribution::Polygon_t remainingpoints =
1155 removeMatchingPoints(rotatednewSet);
1156 LOG(2, "INFO: Remaining points are " << remainingpoints);
1157 return remainingpoints;
1158 } else
1159 return points;
1160}
1161
1162SphericalPointDistribution::PolygonWithIndexTuples
1163SphericalPointDistribution::getAssociatedPoints(
1164 const WeightedPolygon_t &_polygon,
1165 const int _N)
1166{
1167 SphericalPointDistribution::PolygonWithIndexTuples associatedpoints;
1168
1169 // initialze to given number of points
1170 initSelf(_N);
1171 LOG(2, "INFO: Matching old polygon " << _polygon
1172 << " with new polygon " << points);
1173
1174 // check whether there are any points to associate
1175 if (_polygon.empty()) {
1176 associatedpoints.polygon.insert(
1177 associatedpoints.polygon.end(),
1178 points.begin(), points.end());
1179 return associatedpoints;
1180 }
1181
1182 if (_N > 0) {
1183 // combine multiple points and create simple IndexList from IndexTupleList
1184 MatchingControlStructure MCS = findBestMatching(_polygon);
1185 IndexList_t bestmatching = joinPoints(points, MCS.newpoints, MCS.bestmatching);
1186 LOG(2, "INFO: Best matching is " << bestmatching);
1187
1188 // gather the associated points (not the joined ones)
1189 associatedpoints.polygon = MCS.newpoints;
1190 // gather indices
1191 associatedpoints.indices = MCS.bestmatching;
1192
1193 /// now we only need to rotate the associated points to match the given ones
1194 /// with respect to the joined points in points
1195
1196 const size_t NumberIds = std::min(bestmatching.size(), (size_t)3);
1197 // create old set
1198 PolygonWithIndices oldSet;
1199 oldSet.indices.resize(NumberIds, -1);
1200 std::generate(oldSet.indices.begin(), oldSet.indices.end(), UniqueNumber);
1201 for (WeightedPolygon_t::const_iterator iter = _polygon.begin();
1202 iter != _polygon.end(); ++iter)
1203 oldSet.polygon.push_back(iter->first);
1204
1205 // _newpolygon has changed, so now convert to array with matched indices
1206 PolygonWithIndices newSet;
1207 SphericalPointDistribution::IndexList_t::const_iterator beginiter = bestmatching.begin();
1208 SphericalPointDistribution::IndexList_t::const_iterator enditer = bestmatching.begin();
1209 std::advance(enditer, NumberIds);
1210 newSet.indices.resize(NumberIds, -1);
1211 std::copy(beginiter, enditer, newSet.indices.begin());
1212 std::copy(points.begin(),points.end(), std::back_inserter(newSet.polygon));
1213
1214 // determine rotation angles to align the two point distributions with
1215 // respect to bestmatching:
1216 // we use the center between the three first matching points
1217 /// the first rotation brings these two centers to coincide
1218 PolygonWithIndices rotatednewSet = newSet;
1219 {
1220 Rotation_t Rotation = findPlaneAligningRotation(oldSet, newSet);
1221 LOG(5, "DEBUG: Rotating coordinate system by " << Rotation.second
1222 << " around axis " << Rotation.first);
1223 Line Axis(zeroVec, Rotation.first);
1224
1225 // apply rotation angle to bring newCenter to oldCenter in joined points
1226 for (VectorArray_t::iterator iter = rotatednewSet.polygon.begin();
1227 iter != rotatednewSet.polygon.end(); ++iter) {
1228 Vector &current = *iter;
1229 LOG(6, "DEBUG: Original joined point is " << current);
1230 current = Axis.rotateVector(current, Rotation.second);
1231 LOG(6, "DEBUG: Rotated joined point is " << current);
1232 }
1233
1234 // apply rotation angle to the whole set of associated points
1235 for (VectorArray_t::iterator iter = associatedpoints.polygon.begin();
1236 iter != associatedpoints.polygon.end(); ++iter) {
1237 Vector &current = *iter;
1238 LOG(6, "DEBUG: Original associated point is " << current);
1239 current = Axis.rotateVector(current, Rotation.second);
1240 LOG(6, "DEBUG: Rotated associated point is " << current);
1241 }
1242
1243#ifndef NDEBUG
1244 // check: rotated "newCenter" should now equal oldCenter
1245 // we don't check in case of two points as these lie on a great circle
1246 // and the center cannot stably be recalculated. We may reactivate this
1247 // when we calculate centers only once
1248 if (oldSet.indices.size() > 2) {
1249 Vector oldCenter;
1250 Vector rotatednewCenter;
1251 calculateOldAndNewCenters(
1252 oldCenter, rotatednewCenter,
1253 oldSet, rotatednewSet);
1254 oldCenter.Normalize();
1255 rotatednewCenter.Normalize();
1256 // check whether centers are anti-parallel (factor -1)
1257 // then we have the "non-unique poles" situation: points lie on great circle
1258 // and both poles are valid solution
1259 if (fabs(oldCenter.ScalarProduct(rotatednewCenter) + 1.)
1260 < std::numeric_limits<double>::epsilon()*1e4)
1261 rotatednewCenter *= -1.;
1262 LOG(4, "CHECK: rotatednewCenter is " << rotatednewCenter
1263 << ", oldCenter is " << oldCenter);
1264 const double difference = (rotatednewCenter - oldCenter).NormSquared();
1265 ASSERT( difference < std::numeric_limits<double>::epsilon()*1e4,
1266 "matchSphericalPointDistributions() - rotation does not work as expected by "
1267 +toString(difference)+".");
1268 }
1269#endif
1270 }
1271 /// the second (orientation) rotation aligns the planes such that the
1272 /// points themselves coincide
1273 if (bestmatching.size() > 1) {
1274 Rotation_t Rotation = findPointAligningRotation(oldSet, rotatednewSet);
1275
1276 // construct RotationAxis and two points on its plane, defining the angle
1277 Rotation.first.Normalize();
1278 const Line RotationAxis(zeroVec, Rotation.first);
1279
1280 LOG(5, "DEBUG: Rotating around self is " << Rotation.second
1281 << " around axis " << RotationAxis);
1282
1283#ifndef NDEBUG
1284 // check: first bestmatching in rotated_newpolygon and remainingnew
1285 // should now equal
1286 {
1287 const IndexList_t::const_iterator iter = bestmatching.begin();
1288
1289 // check whether both old and newPosition are at same distance to oldCenter
1290 Vector oldCenter = calculateCenter(oldSet);
1291 const double distance = fabs(
1292 (oldSet.polygon[0] - oldCenter).NormSquared()
1293 - (rotatednewSet.polygon[*iter] - oldCenter).NormSquared()
1294 );
1295 LOG(4, "CHECK: Squared distance between oldPosition and newPosition "
1296 << " with respect to oldCenter " << oldCenter << " is " << distance);
1297// ASSERT( distance < warn_amplitude,
1298// "matchSphericalPointDistributions() - old and newPosition's squared distance to oldCenter differs by "
1299// +toString(distance));
1300
1301 Vector rotatednew = RotationAxis.rotateVector(
1302 rotatednewSet.polygon[*iter],
1303 Rotation.second);
1304 LOG(4, "CHECK: rotated first new bestmatching is " << rotatednew
1305 << " while old was " << oldSet.polygon[0]);
1306 const double difference = (rotatednew - oldSet.polygon[0]).NormSquared();
1307 ASSERT( difference < distance+warn_amplitude,
1308 "matchSphericalPointDistributions() - orientation rotation ends up off by "
1309 +toString(difference)+", more than "
1310 +toString(distance+warn_amplitude)+".");
1311 }
1312#endif
1313
1314 // align the set of associated points only here
1315 for (VectorArray_t::iterator iter = associatedpoints.polygon.begin();
1316 iter != associatedpoints.polygon.end(); ++iter) {
1317 Vector &current = *iter;
1318 LOG(6, "DEBUG: Original associated point is " << current);
1319 current = RotationAxis.rotateVector(current, Rotation.second);
1320 LOG(6, "DEBUG: Rotated associated point is " << current);
1321 }
1322 }
1323 }
1324
1325 return associatedpoints;
1326}
1327
1328SphericalPointDistribution::PolygonWithIndexTuples
1329SphericalPointDistribution::getIdentityAssociation(
1330 const WeightedPolygon_t &_polygon)
1331{
1332 unsigned int index = 0;
1333 SphericalPointDistribution::PolygonWithIndexTuples returnpolygon;
1334 for (WeightedPolygon_t::const_iterator iter = _polygon.begin();
1335 iter != _polygon.end(); ++iter, ++index) {
1336 returnpolygon.polygon.push_back( iter->first );
1337 ASSERT( iter->second == 1,
1338 "getIdentityAssociation() - bond with direction "
1339 +toString(iter->second)
1340 +" has degree higher than 1, getIdentityAssociation makes no sense.");
1341 returnpolygon.indices.push_back( IndexList_t(1, index) );
1342 }
1343 return returnpolygon;
1344}
Note: See TracBrowser for help on using the repository browser.