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

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

FAIL: 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.
  • the modified calculateErrorOfMatching() does not work as intended: The idea was to detect the case where a single rotation will make the two distributions coincide. We have unit tests that simulate exactly this case: they rotate the ideal distribution and serve it to the getRemainingPoints(). However, there they are all rotated by the same angle around the same axis. In the modified function we just measure the angle between old and new position irrespective of this (unknown) axis which leads to different angles even if they should be the same if we were to measure it with respect to the original rotation axis. Either we have to fix the rotation axis as well or this ansatz simply does not work as intended.
  • Property mode set to 100644
File size: 50.5 KB
RevLine 
[0d4daf]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"
[90426a]41#include "CodePatterns/Log.hpp"
[0d4daf]42#include "CodePatterns/toString.hpp"
43
44#include <algorithm>
[a2f8a9]45#include <boost/assign.hpp>
[0d4daf]46#include <cmath>
[0d5ca7]47#include <functional>
48#include <iterator>
[0d4daf]49#include <limits>
50#include <list>
[23c605]51#include <numeric>
[0d4daf]52#include <vector>
53#include <map>
54
55#include "LinearAlgebra/Line.hpp"
[3da643]56#include "LinearAlgebra/Plane.hpp"
[0d4daf]57#include "LinearAlgebra/RealSpaceMatrix.hpp"
58#include "LinearAlgebra/Vector.hpp"
59
[a2f8a9]60using namespace boost::assign;
61
[3da643]62// static entities
63const double SphericalPointDistribution::warn_amplitude = 1e-2;
[23c605]64const double SphericalPointDistribution::L1THRESHOLD = 1e-2;
65const double SphericalPointDistribution::L2THRESHOLD = 2e-1;
[3da643]66
[0d4daf]67typedef std::vector<double> DistanceArray_t;
68
[1ae9aa]69// class generator: taken from www.cplusplus.com example std::generate
70struct c_unique {
[23c605]71 unsigned int current;
[1ae9aa]72 c_unique() {current=0;}
[23c605]73 unsigned int operator()() {return current++;}
[1ae9aa]74} UniqueNumber;
75
[23c605]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;
[0d4daf]81
[1ae9aa]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
[a2f8a9]87Vector calculateGeographicMidpoint(
[1ae9aa]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}
[0d4daf]101
[a2f8a9]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
[4a44ed]146 if (_indices.size() == (size_t)1)
147 return _positions[*_indices.begin()];
[a2f8a9]148 // two positions on a line given: return closest point to (1.,0.,0.)
[4a44ed]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;
[a2f8a9]236 }
237 }
238 }
239
240 // start with geographic midpoint
241 center = calculateGeographicMidpoint(_positions, _indices);
242 if (!center.IsZero()) {
243 center.Normalize();
[4a44ed]244 LOG(5, "DEBUG: Starting with geographical midpoint of " << _positions << " under indices "
[a2f8a9]245 << _indices << " is " << center);
246 } else {
247 // any point is good actually
248 center = _positions[0];
[4a44ed]249 LOG(5, "DEBUG: Starting with first position " << center);
[a2f8a9]250 }
251
252 // calculate initial MinimumDistance
253 double MinimumDistance = calculateMinimumDistance(center, _positions, _indices);
[4a44ed]254 LOG(6, "DEBUG: MinimumDistance to this center is " << MinimumDistance);
[a2f8a9]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;
[4a44ed]264 LOG(6, "DEBUG: new MinimumDistance to current test point " << MinimumDistance
[a2f8a9]265 << " is " << center);
266 }
267 }
[4a44ed]268 LOG(6, "DEBUG: new MinimumDistance to center " << center << " is " << MinimumDistance);
[a2f8a9]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;
[4a44ed]296 LOG(7, "DEBUG: new MinimumDistance to test point at " << *angleiter/M_PI*180.
[a2f8a9]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
[39616b]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
[a2f8a9]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
[1cde4e8]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
[23c605]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
[1ae9aa]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);
[23c605]431 ASSERT( !dreiBein.IsZero(), "determineSignOfRotation() - dreiBein is zero.");
[1ae9aa]432 dreiBein.Normalize();
433 const double sign =
434 (dreiBein.ScalarProduct(_newPosition) < 0.) ? -1. : +1.;
435 LOG(6, "DEBUG: oldCenter on plane is " << _oldPosition
[23c605]436 << ", newCenter on plane is " << _newPosition
[1ae9aa]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,
[1cde4e8]448 const SphericalPointDistribution::PolygonWithIndices &_referencepositions,
449 const SphericalPointDistribution::PolygonWithIndices &_currentpositions)
[1ae9aa]450{
[1cde4e8]451 _oldCenter = calculateCenter(_referencepositions.polygon, _referencepositions.indices);
[1ae9aa]452 // C++11 defines a copy_n function ...
[1cde4e8]453 _newCenter = calculateCenter( _currentpositions.polygon, _currentpositions.indices);
[1ae9aa]454}
[0d4daf]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 */
[3da643]466std::pair<double, double> SphericalPointDistribution::calculateErrorOfMatching(
[23c605]467 const VectorArray_t &_old,
468 const VectorArray_t &_new,
469 const IndexTupleList_t &_Matching)
[0d4daf]470{
471 std::pair<double, double> errors( std::make_pair( 0., 0. ) );
472
[7e45c4]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);
[7d8669]485 LOG(6, "DEBUG: Angle between old " << oldcenter << " and new center "
486 << newcenter << " is " << distance);
[7e45c4]487 distances.push_back(distance);
488 mean += distance;
489 }
490 if (!_Matching.empty())
491 mean *= 1./(double)_Matching.size();
492 LOG(5, "DEBUG: Mean distance is " << mean << " for " << _Matching.size() << " points.");
493
494 // analyse errors
495 for (std::vector<double>::const_iterator iter = distances.begin();
496 iter != distances.end(); ++iter) {
497 const double difference = fabs(*iter - mean);
498 if (errors.first < difference) {
499 errors.first = difference;
[23c605]500 }
[7e45c4]501 errors.second += difference*difference;
502
[23c605]503 }
[7e45c4]504 errors.second = sqrt(errors.second);
505
506// if (!_Matching.empty()) {
507// // there is at least one distance, we've checked that before
508// errors.second *= 1./(double)_Matching.size();
509// }
510
511// if (_Matching.size() > 1) {
512// LOG(5, "INFO: Matching is " << _Matching);
513//
514// // calculate all pair-wise distances
515// IndexTupleList_t keys(_old.size(), IndexList_t() );
516// std::generate (keys.begin(), keys.end(), UniqueNumberList);
517//
518// const DistanceArray_t firstdistances = calculatePairwiseDistances(_old, keys);
519// const DistanceArray_t seconddistances = calculatePairwiseDistances(_new, _Matching);
520//
521// ASSERT( firstdistances.size() == seconddistances.size(),
522// "calculateL2ErrorOfMatching() - mismatch in pair-wise distance array sizes.");
523// DistanceArray_t::const_iterator firstiter = firstdistances.begin();
524// DistanceArray_t::const_iterator seconditer = seconddistances.begin();
525// for (;(firstiter != firstdistances.end()) && (seconditer != seconddistances.end());
526// ++firstiter, ++seconditer) {
527// const double gap = fabs(*firstiter - *seconditer);
528// // L1 error
529// if (errors.first < gap)
530// errors.first = gap;
531// // L2 error
532// errors.second += gap*gap;
533// }
534// // there is at least one distance, we've checked that before
535// errors.second *= 1./(double)firstdistances.size();
536// } else {
537// // check whether we have any zero centers: Combining points on new sphere may result
538// // in zero centers
539// for (SphericalPointDistribution::IndexTupleList_t::const_iterator iter = _Matching.begin();
540// iter != _Matching.end(); ++iter) {
541// if ((iter->size() != 1) && (calculateCenter( _new, *iter).IsZero())) {
542// errors.first = 2.;
543// errors.second = 2.;
544// }
545// }
546// }
[23c605]547 LOG(4, "INFO: Resulting errors for matching (L1, L2): "
[90426a]548 << errors.first << "," << errors.second << ".");
[0d4daf]549
550 return errors;
551}
552
[3da643]553SphericalPointDistribution::Polygon_t SphericalPointDistribution::removeMatchingPoints(
[1cde4e8]554 const PolygonWithIndices &_points
[0d4daf]555 )
556{
557 SphericalPointDistribution::Polygon_t remainingpoints;
[1cde4e8]558 IndexArray_t indices(_points.indices.begin(), _points.indices.end());
[0d4daf]559 std::sort(indices.begin(), indices.end());
[90426a]560 LOG(4, "DEBUG: sorted matching is " << indices);
[1cde4e8]561 IndexArray_t remainingindices(_points.polygon.size(), -1);
[bb011f]562 std::generate(remainingindices.begin(), remainingindices.end(), UniqueNumber);
563 IndexArray_t::iterator remainiter = std::set_difference(
564 remainingindices.begin(), remainingindices.end(),
565 indices.begin(), indices.end(),
566 remainingindices.begin());
567 remainingindices.erase(remainiter, remainingindices.end());
568 LOG(4, "DEBUG: remaining indices are " << remainingindices);
569 for (IndexArray_t::const_iterator iter = remainingindices.begin();
570 iter != remainingindices.end(); ++iter) {
[1cde4e8]571 remainingpoints.push_back(_points.polygon[*iter]);
[0d4daf]572 }
573
574 return remainingpoints;
575}
576
577/** Recursive function to go through all possible matchings.
578 *
579 * \param _MCS structure holding global information to the recursion
580 * \param _matching current matching being build up
581 * \param _indices contains still available indices
[23c605]582 * \param _remainingweights current weights to fill (each weight a place)
583 * \param _remainiter iterator over the weights, indicating the current position we match
[0d4daf]584 * \param _matchingsize
585 */
[3da643]586void SphericalPointDistribution::recurseMatchings(
[0d4daf]587 MatchingControlStructure &_MCS,
[23c605]588 IndexTupleList_t &_matching,
[0d4daf]589 IndexList_t _indices,
[23c605]590 WeightsArray_t &_remainingweights,
591 WeightsArray_t::iterator _remainiter,
592 const unsigned int _matchingsize
593 )
[0d4daf]594{
[23c605]595 LOG(5, "DEBUG: Recursing with current matching " << _matching
[90426a]596 << ", remaining indices " << _indices
[23c605]597 << ", and remaining weights " << _matchingsize);
[0d4daf]598 if (!_MCS.foundflag) {
[23c605]599 LOG(5, "DEBUG: Current matching has size " << _matching.size() << ", places left " << _matchingsize);
[0d5ca7]600 if (_matchingsize > 0) {
[0d4daf]601 // go through all indices
602 for (IndexList_t::iterator iter = _indices.begin();
[0d5ca7]603 (iter != _indices.end()) && (!_MCS.foundflag);) {
[e6ca85]604
[23c605]605 // check whether we can stay in the current bin or have to move on to next one
606 if (*_remainiter == 0) {
607 // we need to move on
608 if (_remainiter != _remainingweights.end()) {
609 ++_remainiter;
610 } else {
611 // as we check _matchingsize > 0 this should be impossible
612 ASSERT( 0, "recurseMatchings() - we must not come to this position.");
613 }
614 }
[e6ca85]615
616 // advance in matching to current bin to fill in
[23c605]617 const size_t OldIndex = std::distance(_remainingweights.begin(), _remainiter);
[c56380]618 while (_matching.size() <= OldIndex) { // add empty lists if new bin is opened
[23c605]619 LOG(6, "DEBUG: Extending _matching.");
620 _matching.push_back( IndexList_t() );
621 }
622 IndexTupleList_t::iterator filliniter = _matching.begin();
623 std::advance(filliniter, OldIndex);
[e6ca85]624
625 // check whether connection between bins' indices and candidate is satisfied
[e94448]626 if (!_MCS.adjacency.empty()) {
[e6ca85]627 adjacency_t::const_iterator finder = _MCS.adjacency.find(*iter);
628 ASSERT( finder != _MCS.adjacency.end(),
629 "recurseMatchings() - "+toString(*iter)+" is not in adjacency list.");
630 if ((!filliniter->empty())
631 && (finder->second.find(*filliniter->begin()) == finder->second.end())) {
632 LOG(5, "DEBUG; Skipping index " << *iter
633 << " as is not connected to current set." << *filliniter << ".");
[e94448]634 ++iter; // note that index-loop does not contain incrementor
[e6ca85]635 continue;
636 }
637 }
638
[0d4daf]639 // add index to matching
[23c605]640 filliniter->push_back(*iter);
641 --(*_remainiter);
642 LOG(6, "DEBUG: Adding " << *iter << " to matching at " << OldIndex << ".");
[0d4daf]643 // remove index but keep iterator to position (is the next to erase element)
644 IndexList_t::iterator backupiter = _indices.erase(iter);
645 // recurse with decreased _matchingsize
[23c605]646 recurseMatchings(_MCS, _matching, _indices, _remainingweights, _remainiter, _matchingsize-1);
[0d4daf]647 // re-add chosen index and reset index to new position
[23c605]648 _indices.insert(backupiter, filliniter->back());
[0d4daf]649 iter = backupiter;
650 // remove index from _matching to make space for the next one
[23c605]651 filliniter->pop_back();
652 ++(*_remainiter);
[0d4daf]653 }
654 // gone through all indices then exit recursion
[0d5ca7]655 if (_matching.empty())
656 _MCS.foundflag = true;
[0d4daf]657 } else {
[23c605]658 LOG(4, "INFO: Found matching " << _matching);
[0d4daf]659 // calculate errors
660 std::pair<double, double> errors = calculateErrorOfMatching(
661 _MCS.oldpoints, _MCS.newpoints, _matching);
662 if (errors.first < L1THRESHOLD) {
663 _MCS.bestmatching = _matching;
664 _MCS.foundflag = true;
[0d5ca7]665 } else if (_MCS.bestL2 > errors.second) {
[0d4daf]666 _MCS.bestmatching = _matching;
667 _MCS.bestL2 = errors.second;
668 }
669 }
670 }
671}
672
[e6ca85]673SphericalPointDistribution::MatchingControlStructure::MatchingControlStructure(
674 const adjacency_t &_adjacency,
675 const VectorArray_t &_oldpoints,
676 const VectorArray_t &_newpoints,
677 const WeightsArray_t &_weights
678 ) :
679 foundflag(false),
680 bestL2(std::numeric_limits<double>::max()),
681 adjacency(_adjacency),
682 oldpoints(_oldpoints),
683 newpoints(_newpoints),
684 weights(_weights)
685{}
686
[3da643]687/** Finds combinatorially the best matching between points in \a _polygon
688 * and \a _newpolygon.
689 *
690 * We find the matching with the smallest L2 error, where we break when we stumble
691 * upon a matching with zero error.
692 *
[1ae9aa]693 * As points in \a _polygon may be have a weight greater 1 we have to match it to
694 * multiple points in \a _newpolygon. Eventually, these multiple points are combined
695 * for their center of weight, which is the only thing follow-up function see of
696 * these multiple points. Hence, we actually modify \a _newpolygon in the process
697 * such that the returned IndexList_t indicates a bijective mapping in the end.
698 *
[3da643]699 * \sa recurseMatchings() for going through all matchings
700 *
701 * \param _polygon here, we have indices 0,1,2,...
702 * \param _newpolygon and here we need to find the correct indices
[c8d2e7]703 * \return control structure containing the matching and more
[3da643]704 */
[c8d2e7]705SphericalPointDistribution::MatchingControlStructure
706SphericalPointDistribution::findBestMatching(
[e6ca85]707 const WeightedPolygon_t &_polygon
[3da643]708 )
[0d5ca7]709{
[23c605]710 // transform lists into arrays
[e6ca85]711 VectorArray_t oldpoints;
712 VectorArray_t newpoints;
713 WeightsArray_t weights;
[260540]714 for (WeightedPolygon_t::const_iterator iter = _polygon.begin();
[23c605]715 iter != _polygon.end(); ++iter) {
[e6ca85]716 oldpoints.push_back(iter->first);
717 weights.push_back(iter->second);
[23c605]718 }
[e6ca85]719 newpoints.insert(newpoints.begin(), points.begin(), points.end() );
720 MatchingControlStructure MCS(adjacency, oldpoints, newpoints, weights);
[3da643]721
722 // search for bestmatching combinatorially
723 {
724 // translate polygon into vector to enable index addressing
[e6ca85]725 IndexList_t indices(points.size());
[3da643]726 std::generate(indices.begin(), indices.end(), UniqueNumber);
[23c605]727 IndexTupleList_t matching;
[3da643]728
729 // walk through all matchings
[23c605]730 WeightsArray_t remainingweights = MCS.weights;
731 unsigned int placesleft = std::accumulate(remainingweights.begin(), remainingweights.end(), 0);
732 recurseMatchings(MCS, matching, indices, remainingweights, remainingweights.begin(), placesleft);
733 }
734 if (MCS.foundflag)
735 LOG(3, "Found a best matching beneath L1 threshold of " << L1THRESHOLD);
736 else {
737 if (MCS.bestL2 < warn_amplitude)
738 LOG(3, "Picking matching is " << MCS.bestmatching << " with best L2 error of "
739 << MCS.bestL2);
740 else if (MCS.bestL2 < L2THRESHOLD)
741 ELOG(2, "Picking matching is " << MCS.bestmatching
742 << " with rather large L2 error of " << MCS.bestL2);
743 else
744 ASSERT(0, "findBestMatching() - matching "+toString(MCS.bestmatching)
745 +" has L2 error of "+toString(MCS.bestL2)+" that is too large.");
[0d5ca7]746 }
747
[c8d2e7]748 return MCS;
[3da643]749}
750
[1ae9aa]751SphericalPointDistribution::IndexList_t SphericalPointDistribution::joinPoints(
752 Polygon_t &_newpolygon,
753 const VectorArray_t &_newpoints,
754 const IndexTupleList_t &_bestmatching
755 )
[3da643]756{
[907198]757 // generate trivial index list
758 IndexList_t IndexList(_bestmatching.size(), (size_t)-1);
759 std::generate(IndexList.begin(), IndexList.end(), UniqueNumber);
760 LOG(4, "DEBUG: Our new trivial IndexList reads as " << IndexList);
761
[1ae9aa]762 // combine all multiple points
763 VectorArray_t newCenters;
[907198]764 newCenters.resize(_bestmatching.size());
765 VectorArray_t::iterator centeriter = newCenters.begin();
[1ae9aa]766 for (IndexTupleList_t::const_iterator tupleiter = _bestmatching.begin();
[907198]767 tupleiter != _bestmatching.end(); ++tupleiter, ++centeriter) {
[1ae9aa]768 ASSERT (tupleiter->size() > 0,
769 "findBestMatching() - encountered tuple in bestmatching with size 0.");
770 if (tupleiter->size() == 1) {
771 // add point and index
[907198]772 *centeriter = _newpoints[*tupleiter->begin()];
[1ae9aa]773 } else {
774 // combine into weighted and normalized center
[907198]775 *centeriter = calculateCenter(_newpoints, *tupleiter);
776 (*centeriter).Normalize();
[23c605]777 LOG(5, "DEBUG: Combining " << tupleiter->size() << " points to weighted center "
[907198]778 << *centeriter << ".");
[1ae9aa]779 }
780 }
[907198]781 _newpolygon.insert(_newpolygon.begin(), newCenters.begin(), newCenters.end());
782 LOG(4, "DEBUG: The polygon with centered points is " << _newpolygon);
[1ae9aa]783
784 return IndexList;
[3da643]785}
786
787SphericalPointDistribution::Rotation_t SphericalPointDistribution::findPlaneAligningRotation(
[1cde4e8]788 const PolygonWithIndices &_referencepositions,
789 const PolygonWithIndices &_currentpositions
[3da643]790 )
791{
792 bool dontcheck = false;
793 // initialize to no rotation
794 Rotation_t Rotation;
795 Rotation.first.Zero();
796 Rotation.first[0] = 1.;
797 Rotation.second = 0.;
798
799 // calculate center of triangle/line/point consisting of first points of matching
800 Vector oldCenter;
801 Vector newCenter;
802 calculateOldAndNewCenters(
803 oldCenter, newCenter,
[1cde4e8]804 _referencepositions, _currentpositions);
[3da643]805
[0b517b]806 ASSERT( !oldCenter.IsZero() && !newCenter.IsZero(),
807 "findPlaneAligningRotation() - either old "+toString(oldCenter)
808 +" or new center "+toString(newCenter)+" are zero.");
809 LOG(4, "DEBUG: oldCenter is " << oldCenter << ", newCenter is " << newCenter);
810 if (!oldCenter.IsEqualTo(newCenter)) {
811 // calculate rotation axis and angle
812 Rotation.first = oldCenter;
813 Rotation.first.VectorProduct(newCenter);
814 Rotation.first.Normalize();
815 // construct reference vector to determine direction of rotation
816 const double sign = determineSignOfRotation(newCenter, oldCenter, Rotation.first);
817 Rotation.second = sign * oldCenter.Angle(newCenter);
[3da643]818 } else {
[0b517b]819 // no rotation required anymore
[3da643]820 }
821
822#ifndef NDEBUG
823 // check: rotation brings newCenter onto oldCenter position
824 if (!dontcheck) {
825 Line Axis(zeroVec, Rotation.first);
826 Vector test = Axis.rotateVector(newCenter, Rotation.second);
827 LOG(4, "CHECK: rotated newCenter is " << test
828 << ", oldCenter is " << oldCenter);
829 ASSERT( (test - oldCenter).NormSquared() < std::numeric_limits<double>::epsilon()*1e4,
830 "matchSphericalPointDistributions() - rotation does not work as expected by "
831 +toString((test - oldCenter).NormSquared())+".");
832 }
833#endif
834
835 return Rotation;
836}
837
838SphericalPointDistribution::Rotation_t SphericalPointDistribution::findPointAligningRotation(
[1cde4e8]839 const PolygonWithIndices &remainingold,
840 const PolygonWithIndices &remainingnew)
[3da643]841{
842 // initialize rotation to zero
843 Rotation_t Rotation;
844 Rotation.first.Zero();
845 Rotation.second = 0.;
846
847 // recalculate center
848 Vector oldCenter;
849 Vector newCenter;
850 calculateOldAndNewCenters(
851 oldCenter, newCenter,
[1cde4e8]852 remainingold, remainingnew);
[3da643]853
[39616b]854 // we rotate at oldCenter and around the radial direction, which is again given
855 // by oldCenter.
856 Rotation.first = oldCenter;
857 Rotation.first.Normalize(); // note weighted sum of normalized weight is not normalized
858
859 // calculate center of the rotational plane
860 newCenter = calculateCenterOfGravity(remainingnew.polygon, remainingnew.indices);
861 oldCenter = calculateCenterOfGravity(remainingold.polygon, remainingold.indices);
862 LOG(6, "DEBUG: Using oldCenter " << oldCenter << " as rotation center and "
863 << Rotation.first << " as axis.");
864
865 LOG(6, "DEBUG: old indices are " << remainingold.indices
866 << ", new indices are " << remainingnew.indices);
867 IndexList_t::const_iterator newiter = remainingnew.indices.begin();
868 IndexList_t::const_iterator olditer = remainingold.indices.begin();
869 for (;
870 (newiter != remainingnew.indices.end()) && (olditer != remainingold.indices.end());
871 ++newiter,++olditer) {
872 Vector newPosition = remainingnew.polygon[*newiter];
873 Vector oldPosition = remainingold.polygon[*olditer];
874 LOG(6, "DEBUG: oldPosition is " << oldPosition << " (length: "
875 << oldPosition.Norm() << ") and newPosition is " << newPosition << " length(: "
876 << newPosition.Norm() << ")");
877
878 if (!oldPosition.IsEqualTo(newPosition)) {
879 oldPosition -= oldCenter;
880 newPosition -= newCenter;
881 oldPosition = (oldPosition - oldPosition.Projection(Rotation.first));
882 newPosition = (newPosition - newPosition.Projection(Rotation.first));
883 LOG(6, "DEBUG: Positions after projection are " << oldPosition << " and " << newPosition);
884
885 // construct reference vector to determine direction of rotation
886 const double sign = determineSignOfRotation(newPosition, oldPosition, Rotation.first);
887 LOG(6, "DEBUG: Determining angle between " << oldPosition << " and " << newPosition);
888 const double angle = sign * newPosition.Angle(oldPosition);
889 LOG(6, "DEBUG: Adding " << angle << " to weighted rotation sum.");
890 Rotation.second += angle;
891 } else {
892 LOG(6, "DEBUG: oldPosition and newPosition are equivalent, hence no orientating rotation.");
893 }
[3da643]894 }
[39616b]895 Rotation.second *= 1./(double)remainingnew.indices.size();
[3da643]896
897 return Rotation;
[0d5ca7]898}
899
[e6ca85]900void SphericalPointDistribution::initSelf(const int _NumberOfPoints)
901{
902 switch (_NumberOfPoints)
903 {
904 case 0:
905 points = get<0>();
906 adjacency = getConnections<0>();
907 break;
908 case 1:
909 points = get<1>();
910 adjacency = getConnections<1>();
911 break;
912 case 2:
913 points = get<2>();
914 adjacency = getConnections<2>();
915 break;
916 case 3:
917 points = get<3>();
918 adjacency = getConnections<3>();
919 break;
920 case 4:
921 points = get<4>();
922 adjacency = getConnections<4>();
923 break;
924 case 5:
925 points = get<5>();
926 adjacency = getConnections<5>();
927 break;
928 case 6:
929 points = get<6>();
930 adjacency = getConnections<6>();
931 break;
932 case 7:
933 points = get<7>();
934 adjacency = getConnections<7>();
935 break;
936 case 8:
937 points = get<8>();
938 adjacency = getConnections<8>();
939 break;
940 case 9:
941 points = get<9>();
942 adjacency = getConnections<9>();
943 break;
944 case 10:
945 points = get<10>();
946 adjacency = getConnections<10>();
947 break;
948 case 11:
949 points = get<11>();
950 adjacency = getConnections<11>();
951 break;
952 case 12:
953 points = get<12>();
954 adjacency = getConnections<12>();
955 break;
956 case 14:
957 points = get<14>();
958 adjacency = getConnections<14>();
959 break;
960 default:
961 ASSERT(0, "SphericalPointDistribution::initSelf() - cannot deal with the case "
962 +toString(_NumberOfPoints)+".");
963 }
964 LOG(3, "DEBUG: Ideal polygon is " << points);
965}
[0d5ca7]966
[0d4daf]967SphericalPointDistribution::Polygon_t
[e6ca85]968SphericalPointDistribution::getRemainingPoints(
969 const WeightedPolygon_t &_polygon,
970 const int _N)
[0d4daf]971{
972 SphericalPointDistribution::Polygon_t remainingpoints;
[1cde4e8]973
[e6ca85]974 // initialze to given number of points
975 initSelf(_N);
[0d5ca7]976 LOG(2, "INFO: Matching old polygon " << _polygon
[e6ca85]977 << " with new polygon " << points);
[0d4daf]978
[e6ca85]979 // check whether any points will remain vacant
980 int RemainingPoints = _N;
981 for (WeightedPolygon_t::const_iterator iter = _polygon.begin();
982 iter != _polygon.end(); ++iter)
983 RemainingPoints -= iter->second;
984 if (RemainingPoints == 0)
[3da643]985 return remainingpoints;
[0d4daf]986
[e6ca85]987 if (_N > 0) {
[c8d2e7]988 // combine multiple points and create simple IndexList from IndexTupleList
989 MatchingControlStructure MCS = findBestMatching(_polygon);
990 IndexList_t bestmatching = joinPoints(points, MCS.newpoints, MCS.bestmatching);
[3da643]991 LOG(2, "INFO: Best matching is " << bestmatching);
[0d4daf]992
[1cde4e8]993 const size_t NumberIds = std::min(bestmatching.size(), (size_t)3);
994 // create old set
995 PolygonWithIndices oldSet;
996 oldSet.indices.resize(NumberIds, -1);
997 std::generate(oldSet.indices.begin(), oldSet.indices.end(), UniqueNumber);
998 for (WeightedPolygon_t::const_iterator iter = _polygon.begin();
999 iter != _polygon.end(); ++iter)
1000 oldSet.polygon.push_back(iter->first);
1001
1002 // _newpolygon has changed, so now convert to array with matched indices
1003 PolygonWithIndices newSet;
1004 SphericalPointDistribution::IndexList_t::const_iterator beginiter = bestmatching.begin();
1005 SphericalPointDistribution::IndexList_t::const_iterator enditer = bestmatching.begin();
1006 std::advance(enditer, NumberIds);
1007 newSet.indices.resize(NumberIds, -1);
1008 std::copy(beginiter, enditer, newSet.indices.begin());
[e6ca85]1009 std::copy(points.begin(),points.end(), std::back_inserter(newSet.polygon));
[23c605]1010
[0d4daf]1011 // determine rotation angles to align the two point distributions with
[3da643]1012 // respect to bestmatching:
1013 // we use the center between the three first matching points
1014 /// the first rotation brings these two centers to coincide
[1cde4e8]1015 PolygonWithIndices rotatednewSet = newSet;
[0d4daf]1016 {
[1cde4e8]1017 Rotation_t Rotation = findPlaneAligningRotation(oldSet, newSet);
[3da643]1018 LOG(5, "DEBUG: Rotating coordinate system by " << Rotation.second
1019 << " around axis " << Rotation.first);
1020 Line Axis(zeroVec, Rotation.first);
1021
1022 // apply rotation angle to bring newCenter to oldCenter
[1cde4e8]1023 for (VectorArray_t::iterator iter = rotatednewSet.polygon.begin();
1024 iter != rotatednewSet.polygon.end(); ++iter) {
[3da643]1025 Vector &current = *iter;
1026 LOG(6, "DEBUG: Original point is " << current);
1027 current = Axis.rotateVector(current, Rotation.second);
1028 LOG(6, "DEBUG: Rotated point is " << current);
[0d4daf]1029 }
[3da643]1030
1031#ifndef NDEBUG
1032 // check: rotated "newCenter" should now equal oldCenter
[0b517b]1033 // we don't check in case of two points as these lie on a great circle
1034 // and the center cannot stably be recalculated. We may reactivate this
1035 // when we calculate centers only once
1036 if (oldSet.indices.size() > 2) {
[3da643]1037 Vector oldCenter;
1038 Vector rotatednewCenter;
1039 calculateOldAndNewCenters(
1040 oldCenter, rotatednewCenter,
[1cde4e8]1041 oldSet, rotatednewSet);
[a2f8a9]1042 oldCenter.Normalize();
1043 rotatednewCenter.Normalize();
1044 // check whether centers are anti-parallel (factor -1)
1045 // then we have the "non-unique poles" situation: points lie on great circle
1046 // and both poles are valid solution
1047 if (fabs(oldCenter.ScalarProduct(rotatednewCenter) + 1.)
1048 < std::numeric_limits<double>::epsilon()*1e4)
1049 rotatednewCenter *= -1.;
1050 LOG(4, "CHECK: rotatednewCenter is " << rotatednewCenter
1051 << ", oldCenter is " << oldCenter);
1052 const double difference = (rotatednewCenter - oldCenter).NormSquared();
1053 ASSERT( difference < std::numeric_limits<double>::epsilon()*1e4,
1054 "matchSphericalPointDistributions() - rotation does not work as expected by "
1055 +toString(difference)+".");
[bb011f]1056 }
[3da643]1057#endif
[bb011f]1058 }
[3da643]1059 /// the second (orientation) rotation aligns the planes such that the
1060 /// points themselves coincide
1061 if (bestmatching.size() > 1) {
[1cde4e8]1062 Rotation_t Rotation = findPointAligningRotation(oldSet, rotatednewSet);
[3da643]1063
1064 // construct RotationAxis and two points on its plane, defining the angle
1065 Rotation.first.Normalize();
1066 const Line RotationAxis(zeroVec, Rotation.first);
1067
1068 LOG(5, "DEBUG: Rotating around self is " << Rotation.second
1069 << " around axis " << RotationAxis);
[bb011f]1070
[2d50a2]1071#ifndef NDEBUG
[3da643]1072 // check: first bestmatching in rotated_newpolygon and remainingnew
1073 // should now equal
1074 {
1075 const IndexList_t::const_iterator iter = bestmatching.begin();
[1cde4e8]1076
1077 // check whether both old and newPosition are at same distance to oldCenter
1078 Vector oldCenter = calculateCenter(oldSet);
1079 const double distance = fabs(
1080 (oldSet.polygon[0] - oldCenter).NormSquared()
1081 - (rotatednewSet.polygon[*iter] - oldCenter).NormSquared()
1082 );
1083 LOG(4, "CHECK: Squared distance between oldPosition and newPosition "
1084 << " with respect to oldCenter " << oldCenter << " is " << distance);
1085// ASSERT( distance < warn_amplitude,
1086// "matchSphericalPointDistributions() - old and newPosition's squared distance to oldCenter differs by "
1087// +toString(distance));
1088
[3da643]1089 Vector rotatednew = RotationAxis.rotateVector(
[1cde4e8]1090 rotatednewSet.polygon[*iter],
[3da643]1091 Rotation.second);
1092 LOG(4, "CHECK: rotated first new bestmatching is " << rotatednew
[1cde4e8]1093 << " while old was " << oldSet.polygon[0]);
1094 const double difference = (rotatednew - oldSet.polygon[0]).NormSquared();
[39616b]1095 ASSERT( difference < distance+warn_amplitude,
[1cde4e8]1096 "matchSphericalPointDistributions() - orientation rotation ends up off by "
1097 +toString(difference)+", more than "
[39616b]1098 +toString(distance+warn_amplitude)+".");
[3da643]1099 }
[2d50a2]1100#endif
[0d5ca7]1101
[1cde4e8]1102 for (VectorArray_t::iterator iter = rotatednewSet.polygon.begin();
1103 iter != rotatednewSet.polygon.end(); ++iter) {
[3da643]1104 Vector &current = *iter;
1105 LOG(6, "DEBUG: Original point is " << current);
1106 current = RotationAxis.rotateVector(current, Rotation.second);
1107 LOG(6, "DEBUG: Rotated point is " << current);
[2d50a2]1108 }
1109 }
[0d4daf]1110
1111 // remove all points in matching and return remaining ones
[0d5ca7]1112 SphericalPointDistribution::Polygon_t remainingpoints =
[1cde4e8]1113 removeMatchingPoints(rotatednewSet);
[0d5ca7]1114 LOG(2, "INFO: Remaining points are " << remainingpoints);
1115 return remainingpoints;
[0d4daf]1116 } else
[e6ca85]1117 return points;
[0d4daf]1118}
1119
[ce0ca4]1120SphericalPointDistribution::PolygonWithIndexTuples
1121SphericalPointDistribution::getAssociatedPoints(
1122 const WeightedPolygon_t &_polygon,
1123 const int _N)
1124{
1125 SphericalPointDistribution::PolygonWithIndexTuples associatedpoints;
1126
[2971aa]1127 // initialize to given number of points
[ce0ca4]1128 initSelf(_N);
1129 LOG(2, "INFO: Matching old polygon " << _polygon
1130 << " with new polygon " << points);
1131
1132 // check whether there are any points to associate
1133 if (_polygon.empty()) {
1134 associatedpoints.polygon.insert(
1135 associatedpoints.polygon.end(),
1136 points.begin(), points.end());
1137 return associatedpoints;
1138 }
1139
1140 if (_N > 0) {
1141 // combine multiple points and create simple IndexList from IndexTupleList
1142 MatchingControlStructure MCS = findBestMatching(_polygon);
[907198]1143 points.clear();
[ce0ca4]1144 IndexList_t bestmatching = joinPoints(points, MCS.newpoints, MCS.bestmatching);
[2971aa]1145 LOG(4, "DEBUG: Compare with old polygon " << _polygon);
1146 LOG(2, "INFO: Best matching is " << MCS.bestmatching);
[ce0ca4]1147
1148 // gather the associated points (not the joined ones)
1149 associatedpoints.polygon = MCS.newpoints;
1150 // gather indices
1151 associatedpoints.indices = MCS.bestmatching;
1152
1153 /// now we only need to rotate the associated points to match the given ones
[2971aa]1154 /// with respect to the joined points in \a points
[ce0ca4]1155
1156 const size_t NumberIds = std::min(bestmatching.size(), (size_t)3);
1157 // create old set
1158 PolygonWithIndices oldSet;
1159 oldSet.indices.resize(NumberIds, -1);
1160 std::generate(oldSet.indices.begin(), oldSet.indices.end(), UniqueNumber);
1161 for (WeightedPolygon_t::const_iterator iter = _polygon.begin();
1162 iter != _polygon.end(); ++iter)
1163 oldSet.polygon.push_back(iter->first);
1164
1165 // _newpolygon has changed, so now convert to array with matched indices
1166 PolygonWithIndices newSet;
1167 SphericalPointDistribution::IndexList_t::const_iterator beginiter = bestmatching.begin();
1168 SphericalPointDistribution::IndexList_t::const_iterator enditer = bestmatching.begin();
1169 std::advance(enditer, NumberIds);
1170 newSet.indices.resize(NumberIds, -1);
1171 std::copy(beginiter, enditer, newSet.indices.begin());
1172 std::copy(points.begin(),points.end(), std::back_inserter(newSet.polygon));
1173
1174 // determine rotation angles to align the two point distributions with
1175 // respect to bestmatching:
1176 // we use the center between the three first matching points
1177 /// the first rotation brings these two centers to coincide
1178 PolygonWithIndices rotatednewSet = newSet;
1179 {
1180 Rotation_t Rotation = findPlaneAligningRotation(oldSet, newSet);
1181 LOG(5, "DEBUG: Rotating coordinate system by " << Rotation.second
1182 << " around axis " << Rotation.first);
1183 Line Axis(zeroVec, Rotation.first);
1184
1185 // apply rotation angle to bring newCenter to oldCenter in joined points
1186 for (VectorArray_t::iterator iter = rotatednewSet.polygon.begin();
1187 iter != rotatednewSet.polygon.end(); ++iter) {
1188 Vector &current = *iter;
1189 LOG(6, "DEBUG: Original joined point is " << current);
1190 current = Axis.rotateVector(current, Rotation.second);
1191 LOG(6, "DEBUG: Rotated joined point is " << current);
1192 }
1193
1194 // apply rotation angle to the whole set of associated points
1195 for (VectorArray_t::iterator iter = associatedpoints.polygon.begin();
1196 iter != associatedpoints.polygon.end(); ++iter) {
1197 Vector &current = *iter;
1198 LOG(6, "DEBUG: Original associated point is " << current);
1199 current = Axis.rotateVector(current, Rotation.second);
1200 LOG(6, "DEBUG: Rotated associated point is " << current);
1201 }
1202
1203#ifndef NDEBUG
1204 // check: rotated "newCenter" should now equal oldCenter
1205 // we don't check in case of two points as these lie on a great circle
1206 // and the center cannot stably be recalculated. We may reactivate this
1207 // when we calculate centers only once
1208 if (oldSet.indices.size() > 2) {
1209 Vector oldCenter;
1210 Vector rotatednewCenter;
1211 calculateOldAndNewCenters(
1212 oldCenter, rotatednewCenter,
1213 oldSet, rotatednewSet);
1214 oldCenter.Normalize();
1215 rotatednewCenter.Normalize();
1216 // check whether centers are anti-parallel (factor -1)
1217 // then we have the "non-unique poles" situation: points lie on great circle
1218 // and both poles are valid solution
1219 if (fabs(oldCenter.ScalarProduct(rotatednewCenter) + 1.)
1220 < std::numeric_limits<double>::epsilon()*1e4)
1221 rotatednewCenter *= -1.;
1222 LOG(4, "CHECK: rotatednewCenter is " << rotatednewCenter
1223 << ", oldCenter is " << oldCenter);
1224 const double difference = (rotatednewCenter - oldCenter).NormSquared();
1225 ASSERT( difference < std::numeric_limits<double>::epsilon()*1e4,
1226 "matchSphericalPointDistributions() - rotation does not work as expected by "
1227 +toString(difference)+".");
1228 }
1229#endif
1230 }
1231 /// the second (orientation) rotation aligns the planes such that the
1232 /// points themselves coincide
1233 if (bestmatching.size() > 1) {
1234 Rotation_t Rotation = findPointAligningRotation(oldSet, rotatednewSet);
1235
1236 // construct RotationAxis and two points on its plane, defining the angle
1237 Rotation.first.Normalize();
1238 const Line RotationAxis(zeroVec, Rotation.first);
1239
1240 LOG(5, "DEBUG: Rotating around self is " << Rotation.second
1241 << " around axis " << RotationAxis);
1242
1243#ifndef NDEBUG
1244 // check: first bestmatching in rotated_newpolygon and remainingnew
1245 // should now equal
1246 {
1247 const IndexList_t::const_iterator iter = bestmatching.begin();
1248
1249 // check whether both old and newPosition are at same distance to oldCenter
1250 Vector oldCenter = calculateCenter(oldSet);
1251 const double distance = fabs(
1252 (oldSet.polygon[0] - oldCenter).NormSquared()
1253 - (rotatednewSet.polygon[*iter] - oldCenter).NormSquared()
1254 );
1255 LOG(4, "CHECK: Squared distance between oldPosition and newPosition "
1256 << " with respect to oldCenter " << oldCenter << " is " << distance);
1257// ASSERT( distance < warn_amplitude,
1258// "matchSphericalPointDistributions() - old and newPosition's squared distance to oldCenter differs by "
1259// +toString(distance));
[0d4daf]1260
[ce0ca4]1261 Vector rotatednew = RotationAxis.rotateVector(
1262 rotatednewSet.polygon[*iter],
1263 Rotation.second);
1264 LOG(4, "CHECK: rotated first new bestmatching is " << rotatednew
1265 << " while old was " << oldSet.polygon[0]);
1266 const double difference = (rotatednew - oldSet.polygon[0]).NormSquared();
[39616b]1267 ASSERT( difference < distance+warn_amplitude,
[ce0ca4]1268 "matchSphericalPointDistributions() - orientation rotation ends up off by "
1269 +toString(difference)+", more than "
[39616b]1270 +toString(distance+warn_amplitude)+".");
[ce0ca4]1271 }
1272#endif
1273
1274 // align the set of associated points only here
1275 for (VectorArray_t::iterator iter = associatedpoints.polygon.begin();
1276 iter != associatedpoints.polygon.end(); ++iter) {
1277 Vector &current = *iter;
1278 LOG(6, "DEBUG: Original associated point is " << current);
1279 current = RotationAxis.rotateVector(current, Rotation.second);
1280 LOG(6, "DEBUG: Rotated associated point is " << current);
1281 }
1282 }
1283 }
1284
1285 return associatedpoints;
1286}
1287
1288SphericalPointDistribution::PolygonWithIndexTuples
1289SphericalPointDistribution::getIdentityAssociation(
1290 const WeightedPolygon_t &_polygon)
1291{
1292 unsigned int index = 0;
1293 SphericalPointDistribution::PolygonWithIndexTuples returnpolygon;
1294 for (WeightedPolygon_t::const_iterator iter = _polygon.begin();
1295 iter != _polygon.end(); ++iter, ++index) {
1296 returnpolygon.polygon.push_back( iter->first );
1297 ASSERT( iter->second == 1,
1298 "getIdentityAssociation() - bond with direction "
1299 +toString(iter->second)
1300 +" has degree higher than 1, getIdentityAssociation makes no sense.");
1301 returnpolygon.indices.push_back( IndexList_t(1, index) );
1302 }
1303 return returnpolygon;
1304}
Note: See TracBrowser for help on using the repository browser.