source: src/Fragmentation/Exporters/SphericalPointDistribution.cpp@ 87491e

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

tempcommit: Modified jointPoints() to just combine points and switch to trivial matching.

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