source: src/Fragmentation/Exporters/SphericalPointDistribution.cpp@ 23c605

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