source: src/Fragmentation/Exporters/SphericalPointDistribution.cpp@ e0e77e

Candidate_v1.7.0 stable
Last change on this file since e0e77e was 2390e6, checked in by Frederik Heber <frederik.heber@…>, 4 years ago

SaturateAction may use outer shell information.

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