source: src/Fragmentation/Exporters/SphericalPointDistribution.cpp@ 9cf90e

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

recurseMatching() now takes connections into account.

  • matchSphericalPointDistribution() replaced by getRemainingPoints() as that describes what it does. match...() sounds symmetrical which the function is no longer as connection is associated with former _newpolygon.
  • SphericalPointDistribution now has internal points and adjacency, initialized by initSelf().
  • findBestMatching() and getRemainingPoints() are no longer static functions.
  • all unit tests are working again, including the .._multiple() that tests for joint points due to bond degree greater than 1.
  • the huge case switch in SaturatedFragment::saturateAtom() now resides in initSelf().
  • Property mode set to 100644
File size: 38.6 KB
RevLine 
[f54930]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"
[d468b5]40#include "CodePatterns/IteratorAdaptors.hpp"
[81557e]41#include "CodePatterns/Log.hpp"
[d468b5]42#include "CodePatterns/toString.hpp"
[f54930]43
44#include <algorithm>
[66700f2]45#include <boost/assign.hpp>
[d468b5]46#include <cmath>
[601115]47#include <functional>
48#include <iterator>
[d468b5]49#include <limits>
50#include <list>
[4d611d]51#include <numeric>
[f54930]52#include <vector>
[d468b5]53#include <map>
[f54930]54
55#include "LinearAlgebra/Line.hpp"
[6cf5bb]56#include "LinearAlgebra/Plane.hpp"
[f54930]57#include "LinearAlgebra/RealSpaceMatrix.hpp"
58#include "LinearAlgebra/Vector.hpp"
59
[66700f2]60using namespace boost::assign;
61
[6cf5bb]62// static entities
[81557e]63const double SphericalPointDistribution::SQRT_3(sqrt(3.0));
[6cf5bb]64const double SphericalPointDistribution::warn_amplitude = 1e-2;
[4d611d]65const double SphericalPointDistribution::L1THRESHOLD = 1e-2;
66const double SphericalPointDistribution::L2THRESHOLD = 2e-1;
[6cf5bb]67
68typedef std::vector<double> DistanceArray_t;
[81557e]69
[7e9402]70// class generator: taken from www.cplusplus.com example std::generate
71struct c_unique {
[4d611d]72 unsigned int current;
[7e9402]73 c_unique() {current=0;}
[4d611d]74 unsigned int operator()() {return current++;}
[7e9402]75} UniqueNumber;
76
[4d611d]77struct c_unique_list {
78 unsigned int current;
79 c_unique_list() {current=0;}
80 std::list<unsigned int> operator()() {return std::list<unsigned int>(1, current++);}
81} UniqueNumberList;
[d468b5]82
[7e9402]83/** Calculate the center of a given set of points in \a _positions but only
84 * for those indicated by \a _indices.
85 *
86 */
87inline
[66700f2]88Vector calculateGeographicMidpoint(
[7e9402]89 const SphericalPointDistribution::VectorArray_t &_positions,
90 const SphericalPointDistribution::IndexList_t &_indices)
91{
92 Vector Center;
93 Center.Zero();
94 for (SphericalPointDistribution::IndexList_t::const_iterator iter = _indices.begin();
95 iter != _indices.end(); ++iter)
96 Center += _positions[*iter];
97 if (!_indices.empty())
98 Center *= 1./(double)_indices.size();
99
100 return Center;
101}
[d468b5]102
[66700f2]103inline
104double calculateMinimumDistance(
105 const Vector &_center,
106 const SphericalPointDistribution::VectorArray_t &_points,
107 const SphericalPointDistribution::IndexList_t & _indices)
108{
109 double MinimumDistance = 0.;
110 for (SphericalPointDistribution::IndexList_t::const_iterator iter = _indices.begin();
111 iter != _indices.end(); ++iter) {
112 const double angle = _center.Angle(_points[*iter]);
113 MinimumDistance += angle*angle;
114 }
115 return sqrt(MinimumDistance);
116}
117
118/** Calculates the center of minimum distance for a given set of points \a _points.
119 *
120 * According to http://www.geomidpoint.com/calculation.html this goes a follows:
121 * -# Let CurrentPoint be the geographic midpoint found in Method A. this is used as the starting point for the search.
122 * -# Let MinimumDistance be the sum total of all distances from the current point to all locations in 'Your Places'.
123 * -# 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.
124 * -# Let TestDistance be PI/2 radians (6225 miles or 10018 km).
125 * -# 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.
126 * -# 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.
127 * -# 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.
128 * -# Repeat steps 5 to 7 until no new smallest distance can be found or until TestDistance is less than 0.00000002 radians.
129 *
130 * \param _points set of points
131 * \return Center of minimum distance for all these points, is always of length 1
132 */
133Vector SphericalPointDistribution::calculateCenterOfMinimumDistance(
134 const SphericalPointDistribution::VectorArray_t &_positions,
135 const SphericalPointDistribution::IndexList_t &_indices)
136{
137 ASSERT( _positions.size() >= _indices.size(),
138 "calculateCenterOfMinimumDistance() - less positions than indices given.");
139 Vector center(1.,0.,0.);
140
141 /// first treat some special cases
142 // no positions given: return x axis vector (NOT zero!)
143 {
144 if (_indices.empty())
145 return center;
146 // one position given: return it directly
147 if (_positions.size() == (size_t)1)
148 return _positions[0];
149 // two positions on a line given: return closest point to (1.,0.,0.)
150 if (fabs(_positions[0].ScalarProduct(_positions[1]) + 1.)
151 < std::numeric_limits<double>::epsilon()*1e4) {
152 Vector candidate;
153 if (_positions[0].ScalarProduct(center) > _positions[1].ScalarProduct(center))
154 candidate = _positions[0];
155 else
156 candidate = _positions[1];
157 // non-uniqueness: all positions on great circle, normal to given line are valid
158 // so, we just pick one because returning a unique point is topmost priority
159 Vector normal;
160 normal.GetOneNormalVector(candidate);
161 Vector othernormal = candidate;
162 othernormal.VectorProduct(normal);
163 // now both normal and othernormal describe the plane containing the great circle
164 Plane greatcircle(normal, zeroVec, othernormal);
165 // check which axis is contained and pick the one not
166 if (greatcircle.isContained(center)) {
167 center = Vector(0.,1.,0.);
168 if (greatcircle.isContained(center))
169 center = Vector(0.,0.,1.);
170 // now we are done cause a plane cannot contain all three axis vectors
171 }
172 center = greatcircle.getClosestPoint(candidate);
173 // assure length of 1
174 center.Normalize();
175 }
176 }
177
178 // start with geographic midpoint
179 center = calculateGeographicMidpoint(_positions, _indices);
180 if (!center.IsZero()) {
181 center.Normalize();
182 LOG(4, "DEBUG: Starting with geographical midpoint of " << _positions << " under indices "
183 << _indices << " is " << center);
184 } else {
185 // any point is good actually
186 center = _positions[0];
187 LOG(4, "DEBUG: Starting with first position " << center);
188 }
189
190 // calculate initial MinimumDistance
191 double MinimumDistance = calculateMinimumDistance(center, _positions, _indices);
192 LOG(5, "DEBUG: MinimumDistance to this center is " << MinimumDistance);
193
194 // check all present points whether they may serve as a better center
195 for (SphericalPointDistribution::IndexList_t::const_iterator iter = _indices.begin();
196 iter != _indices.end(); ++iter) {
197 const Vector &centerCandidate = _positions[*iter];
198 const double candidateDistance = calculateMinimumDistance(centerCandidate, _positions, _indices);
199 if (candidateDistance < MinimumDistance) {
200 MinimumDistance = candidateDistance;
201 center = centerCandidate;
202 LOG(5, "DEBUG: new MinimumDistance to current test point " << MinimumDistance
203 << " is " << center);
204 }
205 }
206 LOG(5, "DEBUG: new MinimumDistance to center " << center << " is " << MinimumDistance);
207
208 // now iterate over TestDistance
209 double TestDistance = Vector(1.,0.,0.).Angle(Vector(0.,1.,0.));
210// LOG(6, "DEBUG: initial TestDistance is " << TestDistance);
211
212 const double threshold = sqrt(std::numeric_limits<double>::epsilon());
213 // check each of eight test points at N, NE, E, SE, S, SW, W, NW
214 typedef std::vector<double> angles_t;
215 angles_t testangles;
216 testangles += 0./180.*M_PI, 45./180.*M_PI, 90./180.*M_PI, 135./180.*M_PI,
217 180./180.*M_PI, 225./180.*M_PI, 270./180.*M_PI, 315./180.*M_PI;
218 while (TestDistance > threshold) {
219 Vector OneNormal;
220 OneNormal.GetOneNormalVector(center);
221 Line RotationAxis(zeroVec, OneNormal);
222 Vector North = RotationAxis.rotateVector(center,TestDistance);
223 Line CompassRose(zeroVec, center);
224 bool updatedflag = false;
225 for (angles_t::const_iterator angleiter = testangles.begin();
226 angleiter != testangles.end(); ++angleiter) {
227 Vector centerCandidate = CompassRose.rotateVector(North, *angleiter);
228// centerCandidate.Normalize();
229 const double candidateDistance = calculateMinimumDistance(centerCandidate, _positions, _indices);
230 if (candidateDistance < MinimumDistance) {
231 MinimumDistance = candidateDistance;
232 center = centerCandidate;
233 updatedflag = true;
234 LOG(5, "DEBUG: new MinimumDistance to test point at " << *angleiter/M_PI*180.
235 << "° is " << MinimumDistance);
236 }
237 }
238
239 // if no new point, decrease TestDistance
240 if (!updatedflag) {
241 TestDistance *= 0.5;
242// LOG(6, "DEBUG: TestDistance is now " << TestDistance);
243 }
244 }
245 LOG(4, "DEBUG: Final MinimumDistance to center " << center << " is " << MinimumDistance);
246
247 return center;
248}
249
250Vector calculateCenterOfMinimumDistance(
251 const SphericalPointDistribution::PolygonWithIndices &_points)
252{
253 return SphericalPointDistribution::calculateCenterOfMinimumDistance(_points.polygon, _points.indices);
254}
255
256/** Calculate the center of a given set of points in \a _positions but only
257 * for those indicated by \a _indices.
258 *
259 */
260inline
261Vector calculateCenter(
262 const SphericalPointDistribution::VectorArray_t &_positions,
263 const SphericalPointDistribution::IndexList_t &_indices)
264{
265// Vector Center;
266// Center.Zero();
267// for (SphericalPointDistribution::IndexList_t::const_iterator iter = _indices.begin();
268// iter != _indices.end(); ++iter)
269// Center += _positions[*iter];
270// if (!_indices.empty())
271// Center *= 1./(double)_indices.size();
272//
273// return Center;
274 return SphericalPointDistribution::calculateCenterOfMinimumDistance(_positions, _indices);
275}
276
[4b96da]277/** Calculate the center of a given set of points in \a _positions but only
278 * for those indicated by \a _indices.
279 *
280 */
281inline
282Vector calculateCenter(
283 const SphericalPointDistribution::PolygonWithIndices &_polygon)
284{
285 return calculateCenter(_polygon.polygon, _polygon.indices);
286}
287
[4d611d]288inline
289DistanceArray_t calculatePairwiseDistances(
290 const SphericalPointDistribution::VectorArray_t &_points,
291 const SphericalPointDistribution::IndexTupleList_t &_indices
292 )
293{
294 DistanceArray_t result;
295 for (SphericalPointDistribution::IndexTupleList_t::const_iterator firstiter = _indices.begin();
296 firstiter != _indices.end(); ++firstiter) {
297
298 // calculate first center from possible tuple of indices
299 Vector FirstCenter;
300 ASSERT(!firstiter->empty(), "calculatePairwiseDistances() - there is an empty tuple.");
301 if (firstiter->size() == 1) {
302 FirstCenter = _points[*firstiter->begin()];
303 } else {
304 FirstCenter = calculateCenter( _points, *firstiter);
305 if (!FirstCenter.IsZero())
306 FirstCenter.Normalize();
307 }
308
309 for (SphericalPointDistribution::IndexTupleList_t::const_iterator seconditer = firstiter;
310 seconditer != _indices.end(); ++seconditer) {
311 if (firstiter == seconditer)
312 continue;
313
314 // calculate second center from possible tuple of indices
315 Vector SecondCenter;
316 ASSERT(!seconditer->empty(), "calculatePairwiseDistances() - there is an empty tuple.");
317 if (seconditer->size() == 1) {
318 SecondCenter = _points[*seconditer->begin()];
319 } else {
320 SecondCenter = calculateCenter( _points, *seconditer);
321 if (!SecondCenter.IsZero())
322 SecondCenter.Normalize();
323 }
324
325 // calculate distance between both centers
326 double distance = 2.; // greatest distance on surface of sphere with radius 1.
327 if ((!FirstCenter.IsZero()) && (!SecondCenter.IsZero()))
328 distance = (FirstCenter - SecondCenter).NormSquared();
329 result.push_back(distance);
330 }
331 }
332 return result;
333}
334
[7e9402]335/** Decides by an orthonormal third vector whether the sign of the rotation
336 * angle should be negative or positive.
337 *
338 * \return -1 or 1
339 */
340inline
341double determineSignOfRotation(
342 const Vector &_oldPosition,
343 const Vector &_newPosition,
344 const Vector &_RotationAxis
345 )
346{
347 Vector dreiBein(_oldPosition);
348 dreiBein.VectorProduct(_RotationAxis);
[4d611d]349 ASSERT( !dreiBein.IsZero(), "determineSignOfRotation() - dreiBein is zero.");
[7e9402]350 dreiBein.Normalize();
351 const double sign =
352 (dreiBein.ScalarProduct(_newPosition) < 0.) ? -1. : +1.;
353 LOG(6, "DEBUG: oldCenter on plane is " << _oldPosition
[4d611d]354 << ", newCenter on plane is " << _newPosition
[7e9402]355 << ", and dreiBein is " << dreiBein);
356 return sign;
357}
358
359/** Convenience function to recalculate old and new center for determining plane
360 * rotation.
361 */
362inline
363void calculateOldAndNewCenters(
364 Vector &_oldCenter,
365 Vector &_newCenter,
[4b96da]366 const SphericalPointDistribution::PolygonWithIndices &_referencepositions,
367 const SphericalPointDistribution::PolygonWithIndices &_currentpositions)
[7e9402]368{
[4b96da]369 _oldCenter = calculateCenter(_referencepositions.polygon, _referencepositions.indices);
[7e9402]370 // C++11 defines a copy_n function ...
[4b96da]371 _newCenter = calculateCenter( _currentpositions.polygon, _currentpositions.indices);
[7e9402]372}
[d468b5]373/** Returns squared L2 error of the given \a _Matching.
374 *
375 * We compare the pair-wise distances of each associated matching
376 * and check whether these distances each match between \a _old and
377 * \a _new.
378 *
[81557e]379 * \param _old first set of returnpolygon (fewer or equal to \a _new)
380 * \param _new second set of returnpolygon
[d468b5]381 * \param _Matching matching between the two sets
382 * \return pair with L1 and squared L2 error
383 */
[6cf5bb]384std::pair<double, double> SphericalPointDistribution::calculateErrorOfMatching(
[4d611d]385 const VectorArray_t &_old,
386 const VectorArray_t &_new,
387 const IndexTupleList_t &_Matching)
[d468b5]388{
389 std::pair<double, double> errors( std::make_pair( 0., 0. ) );
390
391 if (_Matching.size() > 1) {
[4d611d]392 LOG(5, "INFO: Matching is " << _Matching);
[d468b5]393
394 // calculate all pair-wise distances
[4d611d]395 IndexTupleList_t keys(_old.size(), IndexList_t() );
396 std::generate (keys.begin(), keys.end(), UniqueNumberList);
397
[d468b5]398 const DistanceArray_t firstdistances = calculatePairwiseDistances(_old, keys);
399 const DistanceArray_t seconddistances = calculatePairwiseDistances(_new, _Matching);
400
401 ASSERT( firstdistances.size() == seconddistances.size(),
402 "calculateL2ErrorOfMatching() - mismatch in pair-wise distance array sizes.");
403 DistanceArray_t::const_iterator firstiter = firstdistances.begin();
404 DistanceArray_t::const_iterator seconditer = seconddistances.begin();
405 for (;(firstiter != firstdistances.end()) && (seconditer != seconddistances.end());
406 ++firstiter, ++seconditer) {
[4d611d]407 const double gap = fabs(*firstiter - *seconditer);
[d468b5]408 // L1 error
409 if (errors.first < gap)
410 errors.first = gap;
411 // L2 error
412 errors.second += gap*gap;
413 }
[4d611d]414 } else {
415 // check whether we have any zero centers: Combining points on new sphere may result
416 // in zero centers
417 for (SphericalPointDistribution::IndexTupleList_t::const_iterator iter = _Matching.begin();
418 iter != _Matching.end(); ++iter) {
419 if ((iter->size() != 1) && (calculateCenter( _new, *iter).IsZero())) {
420 errors.first = 2.;
421 errors.second = 2.;
422 }
423 }
424 }
425 LOG(4, "INFO: Resulting errors for matching (L1, L2): "
[5be870]426 << errors.first << "," << errors.second << ".");
[d468b5]427
428 return errors;
429}
430
[6cf5bb]431SphericalPointDistribution::Polygon_t SphericalPointDistribution::removeMatchingPoints(
[4b96da]432 const PolygonWithIndices &_points
[d468b5]433 )
434{
[4b96da]435 SphericalPointDistribution::Polygon_t remainingpoints;
436 IndexArray_t indices(_points.indices.begin(), _points.indices.end());
[d468b5]437 std::sort(indices.begin(), indices.end());
[5be870]438 LOG(4, "DEBUG: sorted matching is " << indices);
[4b96da]439 IndexArray_t remainingindices(_points.polygon.size(), -1);
[a4d335]440 std::generate(remainingindices.begin(), remainingindices.end(), UniqueNumber);
441 IndexArray_t::iterator remainiter = std::set_difference(
442 remainingindices.begin(), remainingindices.end(),
443 indices.begin(), indices.end(),
444 remainingindices.begin());
445 remainingindices.erase(remainiter, remainingindices.end());
446 LOG(4, "DEBUG: remaining indices are " << remainingindices);
447 for (IndexArray_t::const_iterator iter = remainingindices.begin();
448 iter != remainingindices.end(); ++iter) {
[4b96da]449 remainingpoints.push_back(_points.polygon[*iter]);
[d468b5]450 }
451
[4b96da]452 return remainingpoints;
[d468b5]453}
454
455/** Recursive function to go through all possible matchings.
456 *
457 * \param _MCS structure holding global information to the recursion
458 * \param _matching current matching being build up
459 * \param _indices contains still available indices
[4d611d]460 * \param _remainingweights current weights to fill (each weight a place)
461 * \param _remainiter iterator over the weights, indicating the current position we match
[d468b5]462 * \param _matchingsize
463 */
[6cf5bb]464void SphericalPointDistribution::recurseMatchings(
[d468b5]465 MatchingControlStructure &_MCS,
[4d611d]466 IndexTupleList_t &_matching,
[d468b5]467 IndexList_t _indices,
[4d611d]468 WeightsArray_t &_remainingweights,
469 WeightsArray_t::iterator _remainiter,
470 const unsigned int _matchingsize
471 )
[f54930]472{
[4d611d]473 LOG(5, "DEBUG: Recursing with current matching " << _matching
[5be870]474 << ", remaining indices " << _indices
[4d611d]475 << ", and remaining weights " << _matchingsize);
[d468b5]476 if (!_MCS.foundflag) {
[4d611d]477 LOG(5, "DEBUG: Current matching has size " << _matching.size() << ", places left " << _matchingsize);
[601115]478 if (_matchingsize > 0) {
[d468b5]479 // go through all indices
480 for (IndexList_t::iterator iter = _indices.begin();
[601115]481 (iter != _indices.end()) && (!_MCS.foundflag);) {
[9cf90e]482
[4d611d]483 // check whether we can stay in the current bin or have to move on to next one
484 if (*_remainiter == 0) {
485 // we need to move on
486 if (_remainiter != _remainingweights.end()) {
487 ++_remainiter;
488 } else {
489 // as we check _matchingsize > 0 this should be impossible
490 ASSERT( 0, "recurseMatchings() - we must not come to this position.");
491 }
492 }
[9cf90e]493
494 // advance in matching to current bin to fill in
[4d611d]495 const size_t OldIndex = std::distance(_remainingweights.begin(), _remainiter);
496 while (_matching.size() <= OldIndex) { // add empty lists of new bin is opened
497 LOG(6, "DEBUG: Extending _matching.");
498 _matching.push_back( IndexList_t() );
499 }
500 IndexTupleList_t::iterator filliniter = _matching.begin();
501 std::advance(filliniter, OldIndex);
[9cf90e]502
503 // check whether connection between bins' indices and candidate is satisfied
504 {
505 adjacency_t::const_iterator finder = _MCS.adjacency.find(*iter);
506 ASSERT( finder != _MCS.adjacency.end(),
507 "recurseMatchings() - "+toString(*iter)+" is not in adjacency list.");
508 if ((!filliniter->empty())
509 && (finder->second.find(*filliniter->begin()) == finder->second.end())) {
510 LOG(5, "DEBUG; Skipping index " << *iter
511 << " as is not connected to current set." << *filliniter << ".");
512 ++iter; // note that for loop does not contain incrementor
513 continue;
514 }
515 }
516
[d468b5]517 // add index to matching
[4d611d]518 filliniter->push_back(*iter);
519 --(*_remainiter);
520 LOG(6, "DEBUG: Adding " << *iter << " to matching at " << OldIndex << ".");
[d468b5]521 // remove index but keep iterator to position (is the next to erase element)
522 IndexList_t::iterator backupiter = _indices.erase(iter);
523 // recurse with decreased _matchingsize
[4d611d]524 recurseMatchings(_MCS, _matching, _indices, _remainingweights, _remainiter, _matchingsize-1);
[d468b5]525 // re-add chosen index and reset index to new position
[4d611d]526 _indices.insert(backupiter, filliniter->back());
[d468b5]527 iter = backupiter;
528 // remove index from _matching to make space for the next one
[4d611d]529 filliniter->pop_back();
530 ++(*_remainiter);
[d468b5]531 }
532 // gone through all indices then exit recursion
[601115]533 if (_matching.empty())
534 _MCS.foundflag = true;
[d468b5]535 } else {
[4d611d]536 LOG(4, "INFO: Found matching " << _matching);
[d468b5]537 // calculate errors
538 std::pair<double, double> errors = calculateErrorOfMatching(
[6cf5bb]539 _MCS.oldpoints, _MCS.newpoints, _matching);
[d468b5]540 if (errors.first < L1THRESHOLD) {
541 _MCS.bestmatching = _matching;
542 _MCS.foundflag = true;
[601115]543 } else if (_MCS.bestL2 > errors.second) {
[d468b5]544 _MCS.bestmatching = _matching;
545 _MCS.bestL2 = errors.second;
546 }
547 }
[f54930]548 }
549}
550
[9cf90e]551SphericalPointDistribution::MatchingControlStructure::MatchingControlStructure(
552 const adjacency_t &_adjacency,
553 const VectorArray_t &_oldpoints,
554 const VectorArray_t &_newpoints,
555 const WeightsArray_t &_weights
556 ) :
557 foundflag(false),
558 bestL2(std::numeric_limits<double>::max()),
559 adjacency(_adjacency),
560 oldpoints(_oldpoints),
561 newpoints(_newpoints),
562 weights(_weights)
563{}
564
[6cf5bb]565/** Finds combinatorially the best matching between points in \a _polygon
566 * and \a _newpolygon.
567 *
568 * We find the matching with the smallest L2 error, where we break when we stumble
569 * upon a matching with zero error.
570 *
[7e9402]571 * As points in \a _polygon may be have a weight greater 1 we have to match it to
572 * multiple points in \a _newpolygon. Eventually, these multiple points are combined
573 * for their center of weight, which is the only thing follow-up function see of
574 * these multiple points. Hence, we actually modify \a _newpolygon in the process
575 * such that the returned IndexList_t indicates a bijective mapping in the end.
576 *
[6cf5bb]577 * \sa recurseMatchings() for going through all matchings
578 *
579 * \param _polygon here, we have indices 0,1,2,...
580 * \param _newpolygon and here we need to find the correct indices
581 * \return list of indices: first in \a _polygon goes to first index for \a _newpolygon
582 */
583SphericalPointDistribution::IndexList_t SphericalPointDistribution::findBestMatching(
[9cf90e]584 const WeightedPolygon_t &_polygon
[6cf5bb]585 )
[601115]586{
[4d611d]587 // transform lists into arrays
[9cf90e]588 VectorArray_t oldpoints;
589 VectorArray_t newpoints;
590 WeightsArray_t weights;
[80c119]591 for (WeightedPolygon_t::const_iterator iter = _polygon.begin();
[4d611d]592 iter != _polygon.end(); ++iter) {
[9cf90e]593 oldpoints.push_back(iter->first);
594 weights.push_back(iter->second);
[4d611d]595 }
[9cf90e]596 newpoints.insert(newpoints.begin(), points.begin(), points.end() );
597 MatchingControlStructure MCS(adjacency, oldpoints, newpoints, weights);
[6cf5bb]598
599 // search for bestmatching combinatorially
600 {
601 // translate polygon into vector to enable index addressing
[9cf90e]602 IndexList_t indices(points.size());
[6cf5bb]603 std::generate(indices.begin(), indices.end(), UniqueNumber);
[4d611d]604 IndexTupleList_t matching;
[6cf5bb]605
606 // walk through all matchings
[4d611d]607 WeightsArray_t remainingweights = MCS.weights;
608 unsigned int placesleft = std::accumulate(remainingweights.begin(), remainingweights.end(), 0);
609 recurseMatchings(MCS, matching, indices, remainingweights, remainingweights.begin(), placesleft);
610 }
611 if (MCS.foundflag)
612 LOG(3, "Found a best matching beneath L1 threshold of " << L1THRESHOLD);
613 else {
614 if (MCS.bestL2 < warn_amplitude)
615 LOG(3, "Picking matching is " << MCS.bestmatching << " with best L2 error of "
616 << MCS.bestL2);
617 else if (MCS.bestL2 < L2THRESHOLD)
618 ELOG(2, "Picking matching is " << MCS.bestmatching
619 << " with rather large L2 error of " << MCS.bestL2);
620 else
621 ASSERT(0, "findBestMatching() - matching "+toString(MCS.bestmatching)
622 +" has L2 error of "+toString(MCS.bestL2)+" that is too large.");
[601115]623 }
[6cf5bb]624
[7e9402]625 // combine multiple points and create simple IndexList from IndexTupleList
626 const SphericalPointDistribution::IndexList_t IndexList =
[9cf90e]627 joinPoints(points, MCS.newpoints, MCS.bestmatching);
[6cf5bb]628
[7e9402]629 return IndexList;
[6cf5bb]630}
[601115]631
[7e9402]632SphericalPointDistribution::IndexList_t SphericalPointDistribution::joinPoints(
633 Polygon_t &_newpolygon,
634 const VectorArray_t &_newpoints,
635 const IndexTupleList_t &_bestmatching
636 )
[6cf5bb]637{
[7e9402]638 // combine all multiple points
639 IndexList_t IndexList;
640 IndexArray_t removalpoints;
641 unsigned int UniqueIndex = _newpolygon.size(); // all indices up to size are used right now
642 VectorArray_t newCenters;
643 newCenters.reserve(_bestmatching.size());
644 for (IndexTupleList_t::const_iterator tupleiter = _bestmatching.begin();
645 tupleiter != _bestmatching.end(); ++tupleiter) {
646 ASSERT (tupleiter->size() > 0,
647 "findBestMatching() - encountered tuple in bestmatching with size 0.");
648 if (tupleiter->size() == 1) {
649 // add point and index
650 IndexList.push_back(*tupleiter->begin());
651 } else {
652 // combine into weighted and normalized center
653 Vector Center = calculateCenter(_newpoints, *tupleiter);
654 Center.Normalize();
655 _newpolygon.push_back(Center);
[4d611d]656 LOG(5, "DEBUG: Combining " << tupleiter->size() << " points to weighted center "
[7e9402]657 << Center << " with new index " << UniqueIndex);
658 // mark for removal
659 removalpoints.insert(removalpoints.end(), tupleiter->begin(), tupleiter->end());
660 // add new index
661 IndexList.push_back(UniqueIndex++);
662 }
663 }
664 // IndexList is now our new bestmatching (that is bijective)
665 LOG(4, "DEBUG: Our new bijective IndexList reads as " << IndexList);
666
667 // modifying _newpolygon: remove all points in removalpoints, add those in newCenters
668 Polygon_t allnewpoints = _newpolygon;
669 {
670 _newpolygon.clear();
671 std::sort(removalpoints.begin(), removalpoints.end());
672 size_t i = 0;
673 IndexArray_t::const_iterator removeiter = removalpoints.begin();
674 for (Polygon_t::iterator iter = allnewpoints.begin();
675 iter != allnewpoints.end(); ++iter, ++i) {
676 if ((removeiter != removalpoints.end()) && (i == *removeiter)) {
677 // don't add, go to next remove index
678 ++removeiter;
679 } else {
680 // otherwise add points
681 _newpolygon.push_back(*iter);
682 }
683 }
684 }
685 LOG(4, "DEBUG: The polygon with recentered points removed is " << _newpolygon);
686
687 // map IndexList to new shrinked _newpolygon
688 typedef std::set<unsigned int> IndexSet_t;
689 IndexSet_t SortedIndexList(IndexList.begin(), IndexList.end());
690 IndexList.clear();
691 {
692 size_t offset = 0;
693 IndexSet_t::const_iterator listiter = SortedIndexList.begin();
694 IndexArray_t::const_iterator removeiter = removalpoints.begin();
695 for (size_t i = 0; i < allnewpoints.size(); ++i) {
696 if ((removeiter != removalpoints.end()) && (i == *removeiter)) {
697 ++offset;
698 ++removeiter;
699 } else if ((listiter != SortedIndexList.end()) && (i == *listiter)) {
700 IndexList.push_back(*listiter - offset);
701 ++listiter;
702 }
703 }
704 }
705 LOG(4, "DEBUG: Our new bijective IndexList corrected for removed points reads as "
706 << IndexList);
707
708 return IndexList;
[6cf5bb]709}
710
711SphericalPointDistribution::Rotation_t SphericalPointDistribution::findPlaneAligningRotation(
[4b96da]712 const PolygonWithIndices &_referencepositions,
713 const PolygonWithIndices &_currentpositions
[6cf5bb]714 )
715{
716 bool dontcheck = false;
717 // initialize to no rotation
718 Rotation_t Rotation;
719 Rotation.first.Zero();
720 Rotation.first[0] = 1.;
721 Rotation.second = 0.;
722
723 // calculate center of triangle/line/point consisting of first points of matching
724 Vector oldCenter;
725 Vector newCenter;
726 calculateOldAndNewCenters(
727 oldCenter, newCenter,
[4b96da]728 _referencepositions, _currentpositions);
[6cf5bb]729
[a92c27]730 ASSERT( !oldCenter.IsZero() && !newCenter.IsZero(),
731 "findPlaneAligningRotation() - either old "+toString(oldCenter)
732 +" or new center "+toString(newCenter)+" are zero.");
733 LOG(4, "DEBUG: oldCenter is " << oldCenter << ", newCenter is " << newCenter);
734 if (!oldCenter.IsEqualTo(newCenter)) {
735 // calculate rotation axis and angle
736 Rotation.first = oldCenter;
[a52369]737 if (oldCenter.IsParallelTo(newCenter, 1e-6))
738 Rotation.first.GetOneNormalVector(oldCenter);
739 else {
740 Rotation.first.VectorProduct(newCenter);
741 Rotation.first.Normalize();
742 }
[a92c27]743 // construct reference vector to determine direction of rotation
744 const double sign = determineSignOfRotation(newCenter, oldCenter, Rotation.first);
745 Rotation.second = sign * oldCenter.Angle(newCenter);
[6cf5bb]746 } else {
[a92c27]747 // no rotation required anymore
[6cf5bb]748 }
749
750#ifndef NDEBUG
751 // check: rotation brings newCenter onto oldCenter position
752 if (!dontcheck) {
753 Line Axis(zeroVec, Rotation.first);
754 Vector test = Axis.rotateVector(newCenter, Rotation.second);
755 LOG(4, "CHECK: rotated newCenter is " << test
756 << ", oldCenter is " << oldCenter);
757 ASSERT( (test - oldCenter).NormSquared() < std::numeric_limits<double>::epsilon()*1e4,
758 "matchSphericalPointDistributions() - rotation does not work as expected by "
759 +toString((test - oldCenter).NormSquared())+".");
760 }
761#endif
762
763 return Rotation;
764}
765
766SphericalPointDistribution::Rotation_t SphericalPointDistribution::findPointAligningRotation(
[4b96da]767 const PolygonWithIndices &remainingold,
768 const PolygonWithIndices &remainingnew)
[6cf5bb]769{
770 // initialize rotation to zero
771 Rotation_t Rotation;
772 Rotation.first.Zero();
773 Rotation.first[0] = 1.;
774 Rotation.second = 0.;
775
776 // recalculate center
777 Vector oldCenter;
778 Vector newCenter;
779 calculateOldAndNewCenters(
780 oldCenter, newCenter,
[4b96da]781 remainingold, remainingnew);
[6cf5bb]782
[4b96da]783 Vector oldPosition = remainingnew.polygon[*remainingnew.indices.begin()];
784 Vector newPosition = remainingold.polygon[0];
785 LOG(6, "DEBUG: oldPosition is " << oldPosition << " (length: "
786 << oldPosition.Norm() << ") and newPosition is " << newPosition << " length(: "
787 << newPosition.Norm() << ")");
[a92c27]788
[6cf5bb]789 if (!oldPosition.IsEqualTo(newPosition)) {
[a92c27]790 // we rotate at oldCenter and around the radial direction, which is again given
791 // by oldCenter.
792 Rotation.first = oldCenter;
793 Rotation.first.Normalize(); // note weighted sum of normalized weight is not normalized
794 LOG(6, "DEBUG: Using oldCenter " << oldCenter << " as rotation center and "
795 << Rotation.first << " as axis.");
796 oldPosition -= oldCenter;
797 newPosition -= oldCenter;
798 oldPosition = (oldPosition - oldPosition.Projection(Rotation.first));
799 newPosition = (newPosition - newPosition.Projection(Rotation.first));
800 LOG(6, "DEBUG: Positions after projection are " << oldPosition << " and " << newPosition);
801
[6cf5bb]802 // construct reference vector to determine direction of rotation
803 const double sign = determineSignOfRotation(oldPosition, newPosition, Rotation.first);
804 Rotation.second = sign * oldPosition.Angle(newPosition);
805 } else {
806 LOG(6, "DEBUG: oldPosition and newPosition are equivalent, hence no orientating rotation.");
807 }
808
809 return Rotation;
[601115]810}
811
[9cf90e]812void SphericalPointDistribution::initSelf(const int _NumberOfPoints)
813{
814 switch (_NumberOfPoints)
815 {
816 case 0:
817 points = get<0>();
818 adjacency = getConnections<0>();
819 break;
820 case 1:
821 points = get<1>();
822 adjacency = getConnections<1>();
823 break;
824 case 2:
825 points = get<2>();
826 adjacency = getConnections<2>();
827 break;
828 case 3:
829 points = get<3>();
830 adjacency = getConnections<3>();
831 break;
832 case 4:
833 points = get<4>();
834 adjacency = getConnections<4>();
835 break;
836 case 5:
837 points = get<5>();
838 adjacency = getConnections<5>();
839 break;
840 case 6:
841 points = get<6>();
842 adjacency = getConnections<6>();
843 break;
844 case 7:
845 points = get<7>();
846 adjacency = getConnections<7>();
847 break;
848 case 8:
849 points = get<8>();
850 adjacency = getConnections<8>();
851 break;
852 case 9:
853 points = get<9>();
854 adjacency = getConnections<9>();
855 break;
856 case 10:
857 points = get<10>();
858 adjacency = getConnections<10>();
859 break;
860 case 11:
861 points = get<11>();
862 adjacency = getConnections<11>();
863 break;
864 case 12:
865 points = get<12>();
866 adjacency = getConnections<12>();
867 break;
868 case 14:
869 points = get<14>();
870 adjacency = getConnections<14>();
871 break;
872 default:
873 ASSERT(0, "SphericalPointDistribution::initSelf() - cannot deal with the case "
874 +toString(_NumberOfPoints)+".");
875 }
876 LOG(3, "DEBUG: Ideal polygon is " << points);
877}
[601115]878
[d468b5]879SphericalPointDistribution::Polygon_t
[9cf90e]880SphericalPointDistribution::getRemainingPoints(
881 const WeightedPolygon_t &_polygon,
882 const int _N)
[d468b5]883{
[80c119]884 SphericalPointDistribution::Polygon_t remainingpoints;
[4b96da]885
[9cf90e]886 // initialze to given number of points
887 initSelf(_N);
[601115]888 LOG(2, "INFO: Matching old polygon " << _polygon
[9cf90e]889 << " with new polygon " << points);
[d468b5]890
[9cf90e]891 // check whether any points will remain vacant
892 int RemainingPoints = _N;
893 for (WeightedPolygon_t::const_iterator iter = _polygon.begin();
894 iter != _polygon.end(); ++iter)
895 RemainingPoints -= iter->second;
896 if (RemainingPoints == 0)
[80c119]897 return remainingpoints;
[d468b5]898
[9cf90e]899 if (_N > 0) {
900 IndexList_t bestmatching = findBestMatching(_polygon);
[6cf5bb]901 LOG(2, "INFO: Best matching is " << bestmatching);
[d468b5]902
[4b96da]903 const size_t NumberIds = std::min(bestmatching.size(), (size_t)3);
904 // create old set
905 PolygonWithIndices oldSet;
906 oldSet.indices.resize(NumberIds, -1);
907 std::generate(oldSet.indices.begin(), oldSet.indices.end(), UniqueNumber);
908 for (WeightedPolygon_t::const_iterator iter = _polygon.begin();
909 iter != _polygon.end(); ++iter)
910 oldSet.polygon.push_back(iter->first);
911
912 // _newpolygon has changed, so now convert to array with matched indices
913 PolygonWithIndices newSet;
914 SphericalPointDistribution::IndexList_t::const_iterator beginiter = bestmatching.begin();
915 SphericalPointDistribution::IndexList_t::const_iterator enditer = bestmatching.begin();
916 std::advance(enditer, NumberIds);
917 newSet.indices.resize(NumberIds, -1);
918 std::copy(beginiter, enditer, newSet.indices.begin());
[9cf90e]919 std::copy(points.begin(),points.end(), std::back_inserter(newSet.polygon));
[4d611d]920
[d468b5]921 // determine rotation angles to align the two point distributions with
[6cf5bb]922 // respect to bestmatching:
923 // we use the center between the three first matching points
924 /// the first rotation brings these two centers to coincide
[4b96da]925 PolygonWithIndices rotatednewSet = newSet;
[d468b5]926 {
[4b96da]927 Rotation_t Rotation = findPlaneAligningRotation(oldSet, newSet);
[6cf5bb]928 LOG(5, "DEBUG: Rotating coordinate system by " << Rotation.second
929 << " around axis " << Rotation.first);
930 Line Axis(zeroVec, Rotation.first);
931
932 // apply rotation angle to bring newCenter to oldCenter
[4b96da]933 for (VectorArray_t::iterator iter = rotatednewSet.polygon.begin();
934 iter != rotatednewSet.polygon.end(); ++iter) {
[6cf5bb]935 Vector &current = *iter;
936 LOG(6, "DEBUG: Original point is " << current);
937 current = Axis.rotateVector(current, Rotation.second);
938 LOG(6, "DEBUG: Rotated point is " << current);
[d468b5]939 }
[6cf5bb]940
941#ifndef NDEBUG
942 // check: rotated "newCenter" should now equal oldCenter
[a92c27]943 // we don't check in case of two points as these lie on a great circle
944 // and the center cannot stably be recalculated. We may reactivate this
945 // when we calculate centers only once
946 if (oldSet.indices.size() > 2) {
[6cf5bb]947 Vector oldCenter;
948 Vector rotatednewCenter;
949 calculateOldAndNewCenters(
950 oldCenter, rotatednewCenter,
[4b96da]951 oldSet, rotatednewSet);
[66700f2]952 oldCenter.Normalize();
953 rotatednewCenter.Normalize();
954 // check whether centers are anti-parallel (factor -1)
955 // then we have the "non-unique poles" situation: points lie on great circle
956 // and both poles are valid solution
957 if (fabs(oldCenter.ScalarProduct(rotatednewCenter) + 1.)
958 < std::numeric_limits<double>::epsilon()*1e4)
959 rotatednewCenter *= -1.;
960 LOG(4, "CHECK: rotatednewCenter is " << rotatednewCenter
961 << ", oldCenter is " << oldCenter);
962 const double difference = (rotatednewCenter - oldCenter).NormSquared();
963 ASSERT( difference < std::numeric_limits<double>::epsilon()*1e4,
964 "matchSphericalPointDistributions() - rotation does not work as expected by "
965 +toString(difference)+".");
[a4d335]966 }
[6cf5bb]967#endif
[a4d335]968 }
[6cf5bb]969 /// the second (orientation) rotation aligns the planes such that the
970 /// points themselves coincide
971 if (bestmatching.size() > 1) {
[4b96da]972 Rotation_t Rotation = findPointAligningRotation(oldSet, rotatednewSet);
[6cf5bb]973
974 // construct RotationAxis and two points on its plane, defining the angle
975 Rotation.first.Normalize();
976 const Line RotationAxis(zeroVec, Rotation.first);
977
978 LOG(5, "DEBUG: Rotating around self is " << Rotation.second
979 << " around axis " << RotationAxis);
[a4d335]980
[3237b2]981#ifndef NDEBUG
[6cf5bb]982 // check: first bestmatching in rotated_newpolygon and remainingnew
983 // should now equal
984 {
985 const IndexList_t::const_iterator iter = bestmatching.begin();
[4b96da]986
987 // check whether both old and newPosition are at same distance to oldCenter
988 Vector oldCenter = calculateCenter(oldSet);
989 const double distance = fabs(
990 (oldSet.polygon[0] - oldCenter).NormSquared()
991 - (rotatednewSet.polygon[*iter] - oldCenter).NormSquared()
992 );
993 LOG(4, "CHECK: Squared distance between oldPosition and newPosition "
994 << " with respect to oldCenter " << oldCenter << " is " << distance);
995// ASSERT( distance < warn_amplitude,
996// "matchSphericalPointDistributions() - old and newPosition's squared distance to oldCenter differs by "
997// +toString(distance));
998
[6cf5bb]999 Vector rotatednew = RotationAxis.rotateVector(
[4b96da]1000 rotatednewSet.polygon[*iter],
[6cf5bb]1001 Rotation.second);
1002 LOG(4, "CHECK: rotated first new bestmatching is " << rotatednew
[4b96da]1003 << " while old was " << oldSet.polygon[0]);
1004 const double difference = (rotatednew - oldSet.polygon[0]).NormSquared();
1005 ASSERT( difference < distance+1e-8,
1006 "matchSphericalPointDistributions() - orientation rotation ends up off by "
1007 +toString(difference)+", more than "
1008 +toString(distance+1e-8)+".");
[6cf5bb]1009 }
[3237b2]1010#endif
[601115]1011
[4b96da]1012 for (VectorArray_t::iterator iter = rotatednewSet.polygon.begin();
1013 iter != rotatednewSet.polygon.end(); ++iter) {
[6cf5bb]1014 Vector &current = *iter;
1015 LOG(6, "DEBUG: Original point is " << current);
1016 current = RotationAxis.rotateVector(current, Rotation.second);
1017 LOG(6, "DEBUG: Rotated point is " << current);
[3237b2]1018 }
1019 }
[d468b5]1020
[601115]1021 // remove all points in matching and return remaining ones
1022 SphericalPointDistribution::Polygon_t remainingpoints =
[4b96da]1023 removeMatchingPoints(rotatednewSet);
[601115]1024 LOG(2, "INFO: Remaining points are " << remainingpoints);
1025 return remainingpoints;
[d468b5]1026 } else
[9cf90e]1027 return points;
[d468b5]1028}
Note: See TracBrowser for help on using the repository browser.