source: src/Fragmentation/Exporters/SphericalPointDistribution.cpp@ 4d611d

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

SphericalPointDistribution is now working with bond degree weights.

  • recurseMatching() now works on IndexTupleList_t.
  • also rewrote calculatePairwiseDistances() and calculateErrorOfMatching().
  • L1THRESHOLD in recurseMatching() moved over to class body.
  • increased verbosity level of ...Matching() functions by one, added note on eventually chosen matching and why.
  • we assert that bestL2 is not too large.
  • FIX: calculateErrorOfMatching() did not use absolute value of gap for L1 error.
  • TESTFIX: Using limited accuracy on point coordinates.
  • TESTS: Regresssion test FragmentMolecule-cycles working again.
  • Property mode set to 100644
File size: 30.3 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/math/quaternion.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
60// static entities
61const double SphericalPointDistribution::SQRT_3(sqrt(3.0));
62const double SphericalPointDistribution::warn_amplitude = 1e-2;
63const double SphericalPointDistribution::L1THRESHOLD = 1e-2;
64const double SphericalPointDistribution::L2THRESHOLD = 2e-1;
65
66typedef std::vector<double> DistanceArray_t;
67
68// class generator: taken from www.cplusplus.com example std::generate
69struct c_unique {
70 unsigned int current;
71 c_unique() {current=0;}
72 unsigned int operator()() {return current++;}
73} UniqueNumber;
74
75struct c_unique_list {
76 unsigned int current;
77 c_unique_list() {current=0;}
78 std::list<unsigned int> operator()() {return std::list<unsigned int>(1, current++);}
79} UniqueNumberList;
80
81/** Calculate the center of a given set of points in \a _positions but only
82 * for those indicated by \a _indices.
83 *
84 */
85inline
86Vector calculateCenter(
87 const SphericalPointDistribution::VectorArray_t &_positions,
88 const SphericalPointDistribution::IndexList_t &_indices)
89{
90 Vector Center;
91 Center.Zero();
92 for (SphericalPointDistribution::IndexList_t::const_iterator iter = _indices.begin();
93 iter != _indices.end(); ++iter)
94 Center += _positions[*iter];
95 if (!_indices.empty())
96 Center *= 1./(double)_indices.size();
97
98 return Center;
99}
100
101inline
102DistanceArray_t calculatePairwiseDistances(
103 const SphericalPointDistribution::VectorArray_t &_points,
104 const SphericalPointDistribution::IndexTupleList_t &_indices
105 )
106{
107 DistanceArray_t result;
108 for (SphericalPointDistribution::IndexTupleList_t::const_iterator firstiter = _indices.begin();
109 firstiter != _indices.end(); ++firstiter) {
110
111 // calculate first center from possible tuple of indices
112 Vector FirstCenter;
113 ASSERT(!firstiter->empty(), "calculatePairwiseDistances() - there is an empty tuple.");
114 if (firstiter->size() == 1) {
115 FirstCenter = _points[*firstiter->begin()];
116 } else {
117 FirstCenter = calculateCenter( _points, *firstiter);
118 if (!FirstCenter.IsZero())
119 FirstCenter.Normalize();
120 }
121
122 for (SphericalPointDistribution::IndexTupleList_t::const_iterator seconditer = firstiter;
123 seconditer != _indices.end(); ++seconditer) {
124 if (firstiter == seconditer)
125 continue;
126
127 // calculate second center from possible tuple of indices
128 Vector SecondCenter;
129 ASSERT(!seconditer->empty(), "calculatePairwiseDistances() - there is an empty tuple.");
130 if (seconditer->size() == 1) {
131 SecondCenter = _points[*seconditer->begin()];
132 } else {
133 SecondCenter = calculateCenter( _points, *seconditer);
134 if (!SecondCenter.IsZero())
135 SecondCenter.Normalize();
136 }
137
138 // calculate distance between both centers
139 double distance = 2.; // greatest distance on surface of sphere with radius 1.
140 if ((!FirstCenter.IsZero()) && (!SecondCenter.IsZero()))
141 distance = (FirstCenter - SecondCenter).NormSquared();
142 result.push_back(distance);
143 }
144 }
145 return result;
146}
147
148/** Decides by an orthonormal third vector whether the sign of the rotation
149 * angle should be negative or positive.
150 *
151 * \return -1 or 1
152 */
153inline
154double determineSignOfRotation(
155 const Vector &_oldPosition,
156 const Vector &_newPosition,
157 const Vector &_RotationAxis
158 )
159{
160 Vector dreiBein(_oldPosition);
161 dreiBein.VectorProduct(_RotationAxis);
162 ASSERT( !dreiBein.IsZero(), "determineSignOfRotation() - dreiBein is zero.");
163 dreiBein.Normalize();
164 const double sign =
165 (dreiBein.ScalarProduct(_newPosition) < 0.) ? -1. : +1.;
166 LOG(6, "DEBUG: oldCenter on plane is " << _oldPosition
167 << ", newCenter on plane is " << _newPosition
168 << ", and dreiBein is " << dreiBein);
169 return sign;
170}
171
172/** Convenience function to recalculate old and new center for determining plane
173 * rotation.
174 */
175inline
176void calculateOldAndNewCenters(
177 Vector &_oldCenter,
178 Vector &_newCenter,
179 const SphericalPointDistribution::VectorArray_t &_referencepositions,
180 const SphericalPointDistribution::VectorArray_t &_currentpositions,
181 const SphericalPointDistribution::IndexList_t &_bestmatching)
182{
183 const size_t NumberIds = std::min(_bestmatching.size(), (size_t)3);
184 SphericalPointDistribution::IndexList_t continuousIds(NumberIds, -1);
185 std::generate(continuousIds.begin(), continuousIds.end(), UniqueNumber);
186 _oldCenter = calculateCenter(_referencepositions, continuousIds);
187 // C++11 defines a copy_n function ...
188 SphericalPointDistribution::IndexList_t::const_iterator enditer = _bestmatching.begin();
189 std::advance(enditer, NumberIds);
190 SphericalPointDistribution::IndexList_t firstbestmatchingIds(NumberIds, -1);
191 std::copy(_bestmatching.begin(), enditer, firstbestmatchingIds.begin());
192 _newCenter = calculateCenter( _currentpositions, firstbestmatchingIds);
193}
194/** Returns squared L2 error of the given \a _Matching.
195 *
196 * We compare the pair-wise distances of each associated matching
197 * and check whether these distances each match between \a _old and
198 * \a _new.
199 *
200 * \param _old first set of returnpolygon (fewer or equal to \a _new)
201 * \param _new second set of returnpolygon
202 * \param _Matching matching between the two sets
203 * \return pair with L1 and squared L2 error
204 */
205std::pair<double, double> SphericalPointDistribution::calculateErrorOfMatching(
206 const VectorArray_t &_old,
207 const VectorArray_t &_new,
208 const IndexTupleList_t &_Matching)
209{
210 std::pair<double, double> errors( std::make_pair( 0., 0. ) );
211
212 if (_Matching.size() > 1) {
213 LOG(5, "INFO: Matching is " << _Matching);
214
215 // calculate all pair-wise distances
216 IndexTupleList_t keys(_old.size(), IndexList_t() );
217 std::generate (keys.begin(), keys.end(), UniqueNumberList);
218
219 const DistanceArray_t firstdistances = calculatePairwiseDistances(_old, keys);
220 const DistanceArray_t seconddistances = calculatePairwiseDistances(_new, _Matching);
221
222 ASSERT( firstdistances.size() == seconddistances.size(),
223 "calculateL2ErrorOfMatching() - mismatch in pair-wise distance array sizes.");
224 DistanceArray_t::const_iterator firstiter = firstdistances.begin();
225 DistanceArray_t::const_iterator seconditer = seconddistances.begin();
226 for (;(firstiter != firstdistances.end()) && (seconditer != seconddistances.end());
227 ++firstiter, ++seconditer) {
228 const double gap = fabs(*firstiter - *seconditer);
229 // L1 error
230 if (errors.first < gap)
231 errors.first = gap;
232 // L2 error
233 errors.second += gap*gap;
234 }
235 } else {
236 // check whether we have any zero centers: Combining points on new sphere may result
237 // in zero centers
238 for (SphericalPointDistribution::IndexTupleList_t::const_iterator iter = _Matching.begin();
239 iter != _Matching.end(); ++iter) {
240 if ((iter->size() != 1) && (calculateCenter( _new, *iter).IsZero())) {
241 errors.first = 2.;
242 errors.second = 2.;
243 }
244 }
245 }
246 LOG(4, "INFO: Resulting errors for matching (L1, L2): "
247 << errors.first << "," << errors.second << ".");
248
249 return errors;
250}
251
252SphericalPointDistribution::Polygon_t SphericalPointDistribution::removeMatchingPoints(
253 const VectorArray_t &_points,
254 const IndexList_t &_matchingindices
255 )
256{
257 SphericalPointDistribution::Polygon_t remainingreturnpolygon;
258 IndexArray_t indices(_matchingindices.begin(), _matchingindices.end());
259 std::sort(indices.begin(), indices.end());
260 LOG(4, "DEBUG: sorted matching is " << indices);
261 IndexArray_t remainingindices(_points.size(), -1);
262 std::generate(remainingindices.begin(), remainingindices.end(), UniqueNumber);
263 IndexArray_t::iterator remainiter = std::set_difference(
264 remainingindices.begin(), remainingindices.end(),
265 indices.begin(), indices.end(),
266 remainingindices.begin());
267 remainingindices.erase(remainiter, remainingindices.end());
268 LOG(4, "DEBUG: remaining indices are " << remainingindices);
269 for (IndexArray_t::const_iterator iter = remainingindices.begin();
270 iter != remainingindices.end(); ++iter) {
271 remainingreturnpolygon.push_back(_points[*iter]);
272 }
273
274 return remainingreturnpolygon;
275}
276
277/** Recursive function to go through all possible matchings.
278 *
279 * \param _MCS structure holding global information to the recursion
280 * \param _matching current matching being build up
281 * \param _indices contains still available indices
282 * \param _remainingweights current weights to fill (each weight a place)
283 * \param _remainiter iterator over the weights, indicating the current position we match
284 * \param _matchingsize
285 */
286void SphericalPointDistribution::recurseMatchings(
287 MatchingControlStructure &_MCS,
288 IndexTupleList_t &_matching,
289 IndexList_t _indices,
290 WeightsArray_t &_remainingweights,
291 WeightsArray_t::iterator _remainiter,
292 const unsigned int _matchingsize
293 )
294{
295 LOG(5, "DEBUG: Recursing with current matching " << _matching
296 << ", remaining indices " << _indices
297 << ", and remaining weights " << _matchingsize);
298 if (!_MCS.foundflag) {
299 LOG(5, "DEBUG: Current matching has size " << _matching.size() << ", places left " << _matchingsize);
300 if (_matchingsize > 0) {
301 // go through all indices
302 for (IndexList_t::iterator iter = _indices.begin();
303 (iter != _indices.end()) && (!_MCS.foundflag);) {
304 // check whether we can stay in the current bin or have to move on to next one
305 if (*_remainiter == 0) {
306 // we need to move on
307 if (_remainiter != _remainingweights.end()) {
308 ++_remainiter;
309 } else {
310 // as we check _matchingsize > 0 this should be impossible
311 ASSERT( 0, "recurseMatchings() - we must not come to this position.");
312 }
313 }
314 // advance in matching to same position
315 const size_t OldIndex = std::distance(_remainingweights.begin(), _remainiter);
316 while (_matching.size() <= OldIndex) { // add empty lists of new bin is opened
317 LOG(6, "DEBUG: Extending _matching.");
318 _matching.push_back( IndexList_t() );
319 }
320 IndexTupleList_t::iterator filliniter = _matching.begin();
321 std::advance(filliniter, OldIndex);
322 // add index to matching
323 filliniter->push_back(*iter);
324 --(*_remainiter);
325 LOG(6, "DEBUG: Adding " << *iter << " to matching at " << OldIndex << ".");
326 // remove index but keep iterator to position (is the next to erase element)
327 IndexList_t::iterator backupiter = _indices.erase(iter);
328 // recurse with decreased _matchingsize
329 recurseMatchings(_MCS, _matching, _indices, _remainingweights, _remainiter, _matchingsize-1);
330 // re-add chosen index and reset index to new position
331 _indices.insert(backupiter, filliniter->back());
332 iter = backupiter;
333 // remove index from _matching to make space for the next one
334 filliniter->pop_back();
335 ++(*_remainiter);
336 }
337 // gone through all indices then exit recursion
338 if (_matching.empty())
339 _MCS.foundflag = true;
340 } else {
341 LOG(4, "INFO: Found matching " << _matching);
342 // calculate errors
343 std::pair<double, double> errors = calculateErrorOfMatching(
344 _MCS.oldpoints, _MCS.newpoints, _matching);
345 if (errors.first < L1THRESHOLD) {
346 _MCS.bestmatching = _matching;
347 _MCS.foundflag = true;
348 } else if (_MCS.bestL2 > errors.second) {
349 _MCS.bestmatching = _matching;
350 _MCS.bestL2 = errors.second;
351 }
352 }
353 }
354}
355
356/** Finds combinatorially the best matching between points in \a _polygon
357 * and \a _newpolygon.
358 *
359 * We find the matching with the smallest L2 error, where we break when we stumble
360 * upon a matching with zero error.
361 *
362 * As points in \a _polygon may be have a weight greater 1 we have to match it to
363 * multiple points in \a _newpolygon. Eventually, these multiple points are combined
364 * for their center of weight, which is the only thing follow-up function see of
365 * these multiple points. Hence, we actually modify \a _newpolygon in the process
366 * such that the returned IndexList_t indicates a bijective mapping in the end.
367 *
368 * \sa recurseMatchings() for going through all matchings
369 *
370 * \param _polygon here, we have indices 0,1,2,...
371 * \param _newpolygon and here we need to find the correct indices
372 * \return list of indices: first in \a _polygon goes to first index for \a _newpolygon
373 */
374SphericalPointDistribution::IndexList_t SphericalPointDistribution::findBestMatching(
375 const WeightedPolygon_t &_polygon,
376 Polygon_t &_newpolygon
377 )
378{
379 MatchingControlStructure MCS;
380 MCS.foundflag = false;
381 MCS.bestL2 = std::numeric_limits<double>::max();
382 // transform lists into arrays
383 for (WeightedPolygon_t::const_iterator iter = _polygon.begin();
384 iter != _polygon.end(); ++iter) {
385 MCS.oldpoints.push_back(iter->first);
386 MCS.weights.push_back(iter->second);
387 }
388 MCS.newpoints.insert(MCS.newpoints.begin(), _newpolygon.begin(),_newpolygon.end() );
389
390 // search for bestmatching combinatorially
391 {
392 // translate polygon into vector to enable index addressing
393 IndexList_t indices(_newpolygon.size());
394 std::generate(indices.begin(), indices.end(), UniqueNumber);
395 IndexTupleList_t matching;
396
397 // walk through all matchings
398 WeightsArray_t remainingweights = MCS.weights;
399 unsigned int placesleft = std::accumulate(remainingweights.begin(), remainingweights.end(), 0);
400 recurseMatchings(MCS, matching, indices, remainingweights, remainingweights.begin(), placesleft);
401 }
402 if (MCS.foundflag)
403 LOG(3, "Found a best matching beneath L1 threshold of " << L1THRESHOLD);
404 else {
405 if (MCS.bestL2 < warn_amplitude)
406 LOG(3, "Picking matching is " << MCS.bestmatching << " with best L2 error of "
407 << MCS.bestL2);
408 else if (MCS.bestL2 < L2THRESHOLD)
409 ELOG(2, "Picking matching is " << MCS.bestmatching
410 << " with rather large L2 error of " << MCS.bestL2);
411 else
412 ASSERT(0, "findBestMatching() - matching "+toString(MCS.bestmatching)
413 +" has L2 error of "+toString(MCS.bestL2)+" that is too large.");
414 }
415
416 // combine multiple points and create simple IndexList from IndexTupleList
417 const SphericalPointDistribution::IndexList_t IndexList =
418 joinPoints(_newpolygon, MCS.newpoints, MCS.bestmatching);
419
420 return IndexList;
421}
422
423SphericalPointDistribution::IndexList_t SphericalPointDistribution::joinPoints(
424 Polygon_t &_newpolygon,
425 const VectorArray_t &_newpoints,
426 const IndexTupleList_t &_bestmatching
427 )
428{
429 // combine all multiple points
430 IndexList_t IndexList;
431 IndexArray_t removalpoints;
432 unsigned int UniqueIndex = _newpolygon.size(); // all indices up to size are used right now
433 VectorArray_t newCenters;
434 newCenters.reserve(_bestmatching.size());
435 for (IndexTupleList_t::const_iterator tupleiter = _bestmatching.begin();
436 tupleiter != _bestmatching.end(); ++tupleiter) {
437 ASSERT (tupleiter->size() > 0,
438 "findBestMatching() - encountered tuple in bestmatching with size 0.");
439 if (tupleiter->size() == 1) {
440 // add point and index
441 IndexList.push_back(*tupleiter->begin());
442 } else {
443 // combine into weighted and normalized center
444 Vector Center = calculateCenter(_newpoints, *tupleiter);
445 Center.Normalize();
446 _newpolygon.push_back(Center);
447 LOG(5, "DEBUG: Combining " << tupleiter->size() << " points to weighted center "
448 << Center << " with new index " << UniqueIndex);
449 // mark for removal
450 removalpoints.insert(removalpoints.end(), tupleiter->begin(), tupleiter->end());
451 // add new index
452 IndexList.push_back(UniqueIndex++);
453 }
454 }
455 // IndexList is now our new bestmatching (that is bijective)
456 LOG(4, "DEBUG: Our new bijective IndexList reads as " << IndexList);
457
458 // modifying _newpolygon: remove all points in removalpoints, add those in newCenters
459 Polygon_t allnewpoints = _newpolygon;
460 {
461 _newpolygon.clear();
462 std::sort(removalpoints.begin(), removalpoints.end());
463 size_t i = 0;
464 IndexArray_t::const_iterator removeiter = removalpoints.begin();
465 for (Polygon_t::iterator iter = allnewpoints.begin();
466 iter != allnewpoints.end(); ++iter, ++i) {
467 if ((removeiter != removalpoints.end()) && (i == *removeiter)) {
468 // don't add, go to next remove index
469 ++removeiter;
470 } else {
471 // otherwise add points
472 _newpolygon.push_back(*iter);
473 }
474 }
475 }
476 LOG(4, "DEBUG: The polygon with recentered points removed is " << _newpolygon);
477
478 // map IndexList to new shrinked _newpolygon
479 typedef std::set<unsigned int> IndexSet_t;
480 IndexSet_t SortedIndexList(IndexList.begin(), IndexList.end());
481 IndexList.clear();
482 {
483 size_t offset = 0;
484 IndexSet_t::const_iterator listiter = SortedIndexList.begin();
485 IndexArray_t::const_iterator removeiter = removalpoints.begin();
486 for (size_t i = 0; i < allnewpoints.size(); ++i) {
487 if ((removeiter != removalpoints.end()) && (i == *removeiter)) {
488 ++offset;
489 ++removeiter;
490 } else if ((listiter != SortedIndexList.end()) && (i == *listiter)) {
491 IndexList.push_back(*listiter - offset);
492 ++listiter;
493 }
494 }
495 }
496 LOG(4, "DEBUG: Our new bijective IndexList corrected for removed points reads as "
497 << IndexList);
498
499 return IndexList;
500}
501
502SphericalPointDistribution::Rotation_t SphericalPointDistribution::findPlaneAligningRotation(
503 const VectorArray_t &_referencepositions,
504 const VectorArray_t &_currentpositions,
505 const IndexList_t &_bestmatching
506 )
507{
508 bool dontcheck = false;
509 // initialize to no rotation
510 Rotation_t Rotation;
511 Rotation.first.Zero();
512 Rotation.first[0] = 1.;
513 Rotation.second = 0.;
514
515 // calculate center of triangle/line/point consisting of first points of matching
516 Vector oldCenter;
517 Vector newCenter;
518 calculateOldAndNewCenters(
519 oldCenter, newCenter,
520 _referencepositions, _currentpositions, _bestmatching);
521
522 if ((!oldCenter.IsZero()) && (!newCenter.IsZero())) {
523 LOG(4, "DEBUG: oldCenter is " << oldCenter << ", newCenter is " << newCenter);
524 oldCenter.Normalize();
525 newCenter.Normalize();
526 if (!oldCenter.IsEqualTo(newCenter)) {
527 // calculate rotation axis and angle
528 Rotation.first = oldCenter;
529 Rotation.first.VectorProduct(newCenter);
530 Rotation.second = oldCenter.Angle(newCenter); // /(M_PI/2.);
531 } else {
532 // no rotation required anymore
533 }
534 } else {
535 LOG(4, "DEBUG: oldCenter is " << oldCenter << ", newCenter is " << newCenter);
536 if ((oldCenter.IsZero()) && (newCenter.IsZero())) {
537 // either oldCenter or newCenter (or both) is directly at origin
538 if (_bestmatching.size() == 2) {
539 // line case
540 Vector oldPosition = _currentpositions[*_bestmatching.begin()];
541 Vector newPosition = _referencepositions[0];
542 // check whether we need to rotate at all
543 if (!oldPosition.IsEqualTo(newPosition)) {
544 Rotation.first = oldPosition;
545 Rotation.first.VectorProduct(newPosition);
546 // orientation will fix the sign here eventually
547 Rotation.second = oldPosition.Angle(newPosition);
548 } else {
549 // no rotation required anymore
550 }
551 } else {
552 // triangle case
553 // both triangles/planes have same center, hence get axis by
554 // VectorProduct of Normals
555 Plane newplane(_referencepositions[0], _referencepositions[1], _referencepositions[2]);
556 VectorArray_t vectors;
557 for (IndexList_t::const_iterator iter = _bestmatching.begin();
558 iter != _bestmatching.end(); ++iter)
559 vectors.push_back(_currentpositions[*iter]);
560 Plane oldplane(vectors[0], vectors[1], vectors[2]);
561 Vector oldPosition = oldplane.getNormal();
562 Vector newPosition = newplane.getNormal();
563 // check whether we need to rotate at all
564 if (!oldPosition.IsEqualTo(newPosition)) {
565 Rotation.first = oldPosition;
566 Rotation.first.VectorProduct(newPosition);
567 Rotation.first.Normalize();
568
569 // construct reference vector to determine direction of rotation
570 const double sign = determineSignOfRotation(oldPosition, newPosition, Rotation.first);
571 Rotation.second = sign * oldPosition.Angle(newPosition);
572 LOG(5, "DEBUG: Rotating plane normals by " << Rotation.second
573 << " around axis " << Rotation.first);
574 } else {
575 // else do nothing
576 }
577 }
578 } else {
579 // TODO: we can't do anything here, but this case needs to be dealt with when
580 // we have no ideal geometries anymore
581 if ((oldCenter-newCenter).Norm() > warn_amplitude)
582 ELOG(2, "oldCenter is " << oldCenter << ", yet newCenter is " << newCenter);
583 // else they are considered close enough
584 dontcheck = true;
585 }
586 }
587
588#ifndef NDEBUG
589 // check: rotation brings newCenter onto oldCenter position
590 if (!dontcheck) {
591 Line Axis(zeroVec, Rotation.first);
592 Vector test = Axis.rotateVector(newCenter, Rotation.second);
593 LOG(4, "CHECK: rotated newCenter is " << test
594 << ", oldCenter is " << oldCenter);
595 ASSERT( (test - oldCenter).NormSquared() < std::numeric_limits<double>::epsilon()*1e4,
596 "matchSphericalPointDistributions() - rotation does not work as expected by "
597 +toString((test - oldCenter).NormSquared())+".");
598 }
599#endif
600
601 return Rotation;
602}
603
604SphericalPointDistribution::Rotation_t SphericalPointDistribution::findPointAligningRotation(
605 const VectorArray_t &remainingold,
606 const VectorArray_t &remainingnew,
607 const IndexList_t &_bestmatching)
608{
609 // initialize rotation to zero
610 Rotation_t Rotation;
611 Rotation.first.Zero();
612 Rotation.first[0] = 1.;
613 Rotation.second = 0.;
614
615 // recalculate center
616 Vector oldCenter;
617 Vector newCenter;
618 calculateOldAndNewCenters(
619 oldCenter, newCenter,
620 remainingold, remainingnew, _bestmatching);
621
622 Vector oldPosition = remainingnew[*_bestmatching.begin()];
623 Vector newPosition = remainingold[0];
624 LOG(6, "DEBUG: oldPosition is " << oldPosition << " and newPosition is " << newPosition);
625 if (!oldPosition.IsEqualTo(newPosition)) {
626 if ((!oldCenter.IsZero()) && (!newCenter.IsZero())) {
627 oldCenter.Normalize(); // note weighted sum of normalized weight is not normalized
628 Rotation.first = oldCenter;
629 LOG(6, "DEBUG: Picking normalized oldCenter as Rotation.first " << oldCenter);
630 oldPosition.ProjectOntoPlane(Rotation.first);
631 newPosition.ProjectOntoPlane(Rotation.first);
632 LOG(6, "DEBUG: Positions after projection are " << oldPosition << " and " << newPosition);
633 } else {
634 if (_bestmatching.size() == 2) {
635 // line situation
636 try {
637 Plane oldplane(oldPosition, oldCenter, newPosition);
638 Rotation.first = oldplane.getNormal();
639 LOG(6, "DEBUG: Plane is " << oldplane << " and normal is " << Rotation.first);
640 } catch (LinearDependenceException &e) {
641 LOG(6, "DEBUG: Vectors defining plane are linearly dependent.");
642 // oldPosition and newPosition are on a line, just flip when not equal
643 if (!oldPosition.IsEqualTo(newPosition)) {
644 Rotation.first.Zero();
645 Rotation.first.GetOneNormalVector(oldPosition);
646 LOG(6, "DEBUG: For flipping we use Rotation.first " << Rotation.first);
647 assert( Rotation.first.ScalarProduct(oldPosition) < std::numeric_limits<double>::epsilon()*1e4);
648 // Rotation.second = M_PI;
649 } else {
650 LOG(6, "DEBUG: oldPosition and newPosition are equivalent.");
651 }
652 }
653 } else {
654 // triangle situation
655 Plane oldplane(remainingold[0], remainingold[1], remainingold[2]);
656 Rotation.first = oldplane.getNormal();
657 LOG(6, "DEBUG: oldPlane is " << oldplane << " and normal is " << Rotation.first);
658 oldPosition.ProjectOntoPlane(Rotation.first);
659 LOG(6, "DEBUG: Positions after projection are " << oldPosition << " and " << newPosition);
660 }
661 }
662 // construct reference vector to determine direction of rotation
663 const double sign = determineSignOfRotation(oldPosition, newPosition, Rotation.first);
664 Rotation.second = sign * oldPosition.Angle(newPosition);
665 } else {
666 LOG(6, "DEBUG: oldPosition and newPosition are equivalent, hence no orientating rotation.");
667 }
668
669 return Rotation;
670}
671
672
673SphericalPointDistribution::Polygon_t
674SphericalPointDistribution::matchSphericalPointDistributions(
675 const SphericalPointDistribution::WeightedPolygon_t &_polygon,
676 SphericalPointDistribution::Polygon_t &_newpolygon
677 )
678{
679 SphericalPointDistribution::Polygon_t remainingpoints;
680 VectorArray_t remainingold;
681 for (WeightedPolygon_t::const_iterator iter = _polygon.begin();
682 iter != _polygon.end(); ++iter)
683 remainingold.push_back(iter->first);
684 LOG(2, "INFO: Matching old polygon " << _polygon
685 << " with new polygon " << _newpolygon);
686
687 if (_polygon.size() == _newpolygon.size()) {
688 // same number of points desired as are present? Do nothing
689 LOG(2, "INFO: There are no vacant points to return.");
690 return remainingpoints;
691 }
692
693 if (_polygon.size() > 0) {
694 IndexList_t bestmatching = findBestMatching(_polygon, _newpolygon);
695 LOG(2, "INFO: Best matching is " << bestmatching);
696
697 // _newpolygon has changed, so now convert to array
698 VectorArray_t remainingnew(_newpolygon.begin(), _newpolygon.end());
699
700 // determine rotation angles to align the two point distributions with
701 // respect to bestmatching:
702 // we use the center between the three first matching points
703 /// the first rotation brings these two centers to coincide
704 VectorArray_t rotated_newpolygon = remainingnew;
705 {
706 Rotation_t Rotation = findPlaneAligningRotation(
707 remainingold,
708 remainingnew,
709 bestmatching);
710 LOG(5, "DEBUG: Rotating coordinate system by " << Rotation.second
711 << " around axis " << Rotation.first);
712 Line Axis(zeroVec, Rotation.first);
713
714 // apply rotation angle to bring newCenter to oldCenter
715 for (VectorArray_t::iterator iter = rotated_newpolygon.begin();
716 iter != rotated_newpolygon.end(); ++iter) {
717 Vector &current = *iter;
718 LOG(6, "DEBUG: Original point is " << current);
719 current = Axis.rotateVector(current, Rotation.second);
720 LOG(6, "DEBUG: Rotated point is " << current);
721 }
722
723#ifndef NDEBUG
724 // check: rotated "newCenter" should now equal oldCenter
725 {
726 Vector oldCenter;
727 Vector rotatednewCenter;
728 calculateOldAndNewCenters(
729 oldCenter, rotatednewCenter,
730 remainingold, rotated_newpolygon, bestmatching);
731 // NOTE: Center must not necessarily lie on the sphere with norm 1, hence, we
732 // have to normalize it just as before, as oldCenter and newCenter lengths may differ.
733 if ((!oldCenter.IsZero()) && (!rotatednewCenter.IsZero())) {
734 oldCenter.Normalize();
735 rotatednewCenter.Normalize();
736 LOG(4, "CHECK: rotatednewCenter is " << rotatednewCenter
737 << ", oldCenter is " << oldCenter);
738 ASSERT( (rotatednewCenter - oldCenter).NormSquared() < std::numeric_limits<double>::epsilon()*1e4,
739 "matchSphericalPointDistributions() - rotation does not work as expected by "
740 +toString((rotatednewCenter - oldCenter).NormSquared())+".");
741 }
742 }
743#endif
744 }
745 /// the second (orientation) rotation aligns the planes such that the
746 /// points themselves coincide
747 if (bestmatching.size() > 1) {
748 Rotation_t Rotation = findPointAligningRotation(
749 remainingold,
750 rotated_newpolygon,
751 bestmatching);
752
753 // construct RotationAxis and two points on its plane, defining the angle
754 Rotation.first.Normalize();
755 const Line RotationAxis(zeroVec, Rotation.first);
756
757 LOG(5, "DEBUG: Rotating around self is " << Rotation.second
758 << " around axis " << RotationAxis);
759
760#ifndef NDEBUG
761 // check: first bestmatching in rotated_newpolygon and remainingnew
762 // should now equal
763 {
764 const IndexList_t::const_iterator iter = bestmatching.begin();
765 Vector rotatednew = RotationAxis.rotateVector(
766 rotated_newpolygon[*iter],
767 Rotation.second);
768 LOG(4, "CHECK: rotated first new bestmatching is " << rotatednew
769 << " while old was " << remainingold[0]);
770 ASSERT( (rotatednew - remainingold[0]).Norm() < warn_amplitude,
771 "matchSphericalPointDistributions() - orientation rotation ends up off by more than "
772 +toString(warn_amplitude)+".");
773 }
774#endif
775
776 for (VectorArray_t::iterator iter = rotated_newpolygon.begin();
777 iter != rotated_newpolygon.end(); ++iter) {
778 Vector &current = *iter;
779 LOG(6, "DEBUG: Original point is " << current);
780 current = RotationAxis.rotateVector(current, Rotation.second);
781 LOG(6, "DEBUG: Rotated point is " << current);
782 }
783 }
784
785 // remove all points in matching and return remaining ones
786 SphericalPointDistribution::Polygon_t remainingpoints =
787 removeMatchingPoints(rotated_newpolygon, bestmatching);
788 LOG(2, "INFO: Remaining points are " << remainingpoints);
789 return remainingpoints;
790 } else
791 return _newpolygon;
792}
Note: See TracBrowser for help on using the repository browser.