libatomprobe
Library for Atom Probe Tomography (APT) computation
rotations.cpp
Go to the documentation of this file.
1 /* rotations.cpp : Mass spectrum candidate filtering
2  * Copyright (C) 2020 Daniel Haley
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
19 
20 #include "../helper/helpFuncs.h"
22 
23 #include <gsl/gsl_blas.h>
24 #include <gsl/gsl_linalg.h>
25 
26 #ifdef DEBUG
27 #include <random>
29 #endif
30 
31 namespace AtomProbe
32 {
33 
34 //TODO: Move to general maths file
35 //Compute determinant of gsl_matrix. A is replaced by LU decomposition
36 // if inPlace = true
37 double gsl_matrix_det(gsl_matrix *A, bool inPlace=false)
38 {
39  /*
40  inPlace = 1 => A is replaced with the LU decomposed copy.
41  inPlace = 0 => A is retained, and a copy is used for LU.
42  */
43 
44  double det;
45  int signum;
46  gsl_permutation *p = gsl_permutation_alloc(A->size1);
47  gsl_matrix *tmpA;
48 
49  if (inPlace)
50  tmpA = A;
51  else {
52  tmpA = gsl_matrix_alloc(A->size1, A->size2);
53  gsl_matrix_memcpy(tmpA, A);
54  }
55 
56 
57  gsl_linalg_LU_decomp(tmpA, p, &signum);
58  det = gsl_linalg_LU_det(tmpA, signum);
59  gsl_permutation_free(p);
60  if (! inPlace)
61  gsl_matrix_free(tmpA);
62 
63 
64  return det;
65 }
66 
67 void computeRotationMatrix(const Point3D &ur1, const Point3D &ur2,
68  const Point3D &r1, const Point3D &r2, std::vector<std:: vector<float> > &m)
69 {
70  gsl_matrix *mm = gsl_matrix_alloc(3,3);
71  computeRotationMatrix(ur1,ur2,r1,r2,mm);
72  m.clear();
73  m.resize(3);
74  for(auto ui=0;ui<3;ui++)
75  {
76  m[ui].resize(3);
77  for(auto uj=0;uj<3;uj++)
78  m[ui][uj] =gsl_matrix_get(mm,ui,uj);
79  }
80 
81 };
82 
83 void computeRotationMatrix(const Point3D &ur1, const Point3D &ur2,
84  const Point3D &r1, const Point3D &r2, gsl_matrix *m)
85 {
86  //TRIAD algorithm, for determining rotation matrix from two
87  // linearly independant vector pairs.
88  // This is a specific case of "Wahba's Problem", where no noise is present
89 
90  //vectors should be pre-normalised
91  ASSERT(TOL_EQ(ur1.sqrMag(),1) && TOL_EQ(ur2.sqrMag(),1));
92  ASSERT(TOL_EQ(r1.sqrMag(),1) && TOL_EQ(r2.sqrMag(),1));
93 
94  //unrotated and rotated vectors should be linearly independant
95  ASSERT(ur1.crossProd(ur2).sqrMag() -1 < 0.001);
96  ASSERT(r1.crossProd(r2).sqrMag() -1 < 0.001);
97 
98 
99  ASSERT(m->size1== 3);
100  ASSERT(m->size2== 3);
101 
102  //r1 x r2 and ur1 x ur2
103  Point3D rCross,urCross;
104  rCross = r1.crossProd(r2);
105  urCross = ur1.crossProd(ur2);
106 
107  //reorthogonalise
108  Point3D doubleCross1,doubleCross2;
109  doubleCross1 = r1.crossProd(rCross);
110  doubleCross2 = ur1.crossProd(urCross);
111 
112  gsl_matrix *a,*b;
113  a = gsl_matrix_alloc(3,3);
114  b = gsl_matrix_alloc(3,3);
115 
116  //Build the two matrices used in the triad
117  for(unsigned int ui=0;ui<3;ui++)
118  {
119  //build A matrix, row by row
120  gsl_matrix_set(a,ui,0,r1[ui]);
121  gsl_matrix_set(a,ui,1,rCross[ui]);
122  gsl_matrix_set(a,ui,2,doubleCross1[ui]);
123 
124  //build (B^T) matrix, col by col
125  gsl_matrix_set(b,0,ui,ur1[ui]);
126  gsl_matrix_set(b,1,ui,urCross[ui]);
127  gsl_matrix_set(b,2,ui,doubleCross2[ui]);
128 
129  }
130 
131  //The two matrices should be unit determinant
132  ASSERT(TOL_EQ(fabs(gsl_determinant(a)),1));
133  ASSERT(TOL_EQ(fabs(gsl_determinant(b)),1));
134 
135  //Compute m = a*b; where m is the rotation matrix
136  gsl_blas_dgemm (CblasNoTrans, CblasNoTrans,
137  1.0, a, b,
138  0.0, m);
139 
140 
141  gsl_matrix_free(a);
142  gsl_matrix_free(b);
143 
144 }
145 
146 
147 unsigned int computeRotationMatrixWahba(const std::vector<Point3D> &unrotated,const std::vector<Point3D> &rotated,
148  const std::vector<float> &weights,gsl_matrix* &R)
149 {
150 
151  //Matched vector sizes
152  ASSERT(rotated.size() == unrotated.size());
153  //constrained problem
154  ASSERT(rotated.size() >=2);
155 
156  if(rotated.size() < 2)
157  return 1;
158 
159  if(R == nullptr)
160  R=gsl_matrix_alloc(3,3);
161  else
162  {
163  ASSERT(R->size1 == 3 && R->size2 ==3);
164  }
165 
166  //See "attitude determination using vector observations and the singular value decomposition"
167  // Markley, L. Journal of the Astronautical Sciences, Nov 1987
168  //We need to find a matrix , B, which is of the form
169  // B = sum[ k_i *(a_i w_i v_i') ]
170  // where v_i is the pre-rotation vectors, and w_i are the post-rotation vectors
171  // k_i are the weights for the measurement (e.g. inverse error)
172 
173  gsl_matrix *B = gsl_matrix_alloc(3,3);
174  gsl_matrix_set_all(B,0);
175  gsl_vector *v1,*v2;
176  v1= gsl_vector_alloc(3);
177  v2= gsl_vector_alloc(3);
178 
179  for(auto ui=0u; ui<unrotated.size();ui++)
180  {
181  ASSERT(TOL_EQ(unrotated[ui].sqrMag(),1));
182  ASSERT(TOL_EQ(rotated[ui].sqrMag(),1));
183  for(auto uj=0u;uj<3;uj++)
184  {
185  v1->data[uj] = unrotated[ui][uj];
186  v2->data[uj] = rotated[ui][uj];
187  }
188 
189  //Sum outer products
190  gsl_blas_dger(weights[ui],v1, v2, B);
191  }
192 
193  gsl_vector_free(v1);
194  gsl_vector_free(v2);
195 
196  //OK. so now we have the B matrix, decompose
197  // to obtain U and V
198 
199  //Allocate output area for SVD
200  gsl_matrix *V = gsl_matrix_alloc(B->size2,B->size2);
201  gsl_vector *S = gsl_vector_alloc(B->size2);
202  gsl_vector *work = gsl_vector_alloc(B->size2);
203 
204  //On output, B is replaced by "U"
205  gsl_linalg_SV_decomp (B, V, S, work);
206 
207  gsl_vector_free(work);
208 
209  //FIXME: Should check for singularity
210  //We don't actually use the sv's themselves
211  gsl_vector_free(S);
212 
213  // M = diag([1 1 det(U) det(V)])
214  gsl_matrix *M=gsl_matrix_alloc(3,3);
215  gsl_matrix_set_all(M,0);
216 
217  //Fill M
218  gsl_matrix_set(M,0,0,1);
219  gsl_matrix_set(M,1,1,1);
220  gsl_matrix_set(M,2,2,gsl_determinant(B)*gsl_determinant(V));
221 
222  //Now compute R = U M V'
223  //T= U*M, recall B is reassigned as U
224  gsl_matrix *T=gsl_matrix_alloc(3,3);
225  gsl_matrix_mult(B,M,T);
226 
227  //R=T*V'
228  gsl_matrix_transpose(V);
229  gsl_matrix_mult(T,V,R,false);
230 
231 
232  //Compared to other routines in this library, the transformation here
233  // is actually the inverse rotation, R', so transpose to get R
234  gsl_matrix_transpose(R);
235 
236  gsl_matrix_free(V);
237  gsl_matrix_free(B);
238  gsl_matrix_free(T);
239  gsl_matrix_free(M);
240 
241  return 0;
242 
243 }
244 
245 //Return the rotation matrix in axis-angle (pt is axis, theta is angle)
246 gsl_matrix *getRotationMatrix(const Point3D &pt, float theta)
247 {
248  Point3D norm =pt;
249  norm.normalise();
250 
251  gsl_matrix *R = gsl_matrix_alloc(3,3);
252 
253  double versin = 1.0-cos(theta);
254  double ct = cos(theta);
255  double st= sin(theta);
256 
257  //set diagonal
258  for(auto ui=0;ui<3;ui++)
259  gsl_matrix_set(R,ui,ui,ct+norm[ui]*norm[ui]*versin);
260 
261  //top row
262  gsl_matrix_set(R,0,1,norm[0]*norm[1]*versin - norm[2]*st); //1
263  gsl_matrix_set(R,0,2,norm[0]*norm[2]*versin + norm[1]*st); //2
264 
265  //middle row
266  gsl_matrix_set(R,1,0,norm[1]*norm[0]*versin+norm[2]*st); //0
267  gsl_matrix_set(R,1,2,norm[1]*norm[2]*versin-norm[0]*st); //2
268 
269  //bottom row
270  gsl_matrix_set(R,2,0,norm[2]*norm[0]*versin-norm[1]*st); //0
271  gsl_matrix_set(R,2,1,norm[2]*norm[1]*versin+norm[0]*st); //1
272 
273  //Determinant should be 1
274  ASSERT(TOL_EQ(gsl_determinant(R),1));
275 
276  return R;
277 }
278 
279 
280 #ifdef DEBUG
281 bool testRotationAlgorithms();
282 bool testTRIADRotation();
283 bool testWahba();
284 
285 
286 
287 bool testRotationAlgorithms()
288 {
289  if(!testTRIADRotation())
290  return false;
291  if(!testWahba())
292  return false;
293 
294  return true;
295 }
296 
297 bool testTRIADRotation()
298 {
299 
300  Point3D pZ = Point3D(0,0,1);
301  Point3D pY = Point3D(0,1,0);
302 
303  Point3D prY = Point3D(0,1,0);
304  Point3D prZ = Point3D(1,0,1);
305  prY.normalise();
306  prZ.normalise();
307 
308  gsl_matrix *m;
309  m = gsl_matrix_alloc(3,3);
310 
311  computeRotationMatrix(pY,pZ,prY,prZ,m);
312  TEST(TOL_EQ(fabs(gsl_determinant(m)),1.0),"Unit determinant");
313 
314  //If we transform the original vectors to their new orientaitons
315  // we should get the new vectors back out
316  pZ.transform3x3(m);
317  pY.transform3x3(m);
318 
319  TEST(pZ.sqrDist(prZ) < 0.01,"Rotate test");
320  TEST(pY.sqrDist(prY) < 0.01,"Rotate test");
321 
322  gsl_matrix_free(m);
323 
324  return true;
325 }
326 
327 bool testWahba()
328 {
329  const unsigned int NPTS=10;
330 
331  std::random_device rd; //Will be used to obtain a seed for the random number engine
332  std::mt19937 gen(rd()); //Standard mersenne_twister_engine seeded with rd()
333  std::uniform_real_distribution<> distrib(0, 1);
334 
335  std::vector<Point3D> pts,rotatedPts;
336  pts.resize(NPTS);
337  for(auto ui=0;ui<NPTS;ui++)
338  {
339  pts[ui] = Point3D(distrib(gen),
340  distrib(gen),
341  distrib(gen));
342 
343  pts[ui].normalise();
344  }
345 
346  //Perform null rotation
347  rotatedPts = pts;
348 
349  std::vector<float> weights;
350  weights.resize(pts.size(),1);
351 
352  gsl_matrix *R=nullptr;
353  computeRotationMatrixWahba(pts,rotatedPts,weights,R);
354 
355  //Check for identity
356  for(auto ui=0;ui<3;ui++)
357  {
358  for(auto uj=0;uj<3;uj++)
359  {
360  if(ui == uj)
361  {
362  TEST(TOL_EQ(gsl_matrix_get(R,ui,uj),1.0),"Identity matrix");
363  }
364  else
365  {
366  TEST(TOL_EQ(gsl_matrix_get(R,ui,uj),0), "Identity matrix (1)");
367  }
368  }
369  }
370 
371  gsl_matrix_free(R);
372 
373  //Compute a rotation matrix
374  Point3D axis=Point3D(1,2,3);
375  axis.normalise();
376  float angle=0.51;
377  R=getRotationMatrix(axis,angle);
378 
379 
380 
381  //Rotate each point by the same axis-angle
382  for(auto &p : rotatedPts)
383  quat_rot(p,axis,angle);
384 
385  //Retrieve rotation matrix
386  gsl_matrix *R_computed=nullptr;
387  computeRotationMatrixWahba(pts,rotatedPts,weights,R_computed);
388 
389  for(auto ui=0u; ui<9;ui++)
390  {
391  ASSERT( TOL_EQ(R_computed->data[ui],R->data[ui]));
392  }
393 
394  gsl_matrix_free(R);
395  gsl_matrix_free(R_computed);
396  return true;
397 
398 }
399 
400 #endif
401 
402 }
float sqrDist(const Point3D &pt) const
Returns the square of distance to another Point3D.
Definition: point3D.cpp:272
void computeRotationMatrix(const Point3D &ur1, const Point3D &ur2, const Point3D &r1, const Point3D &r2, gsl_matrix *m)
Definition: rotations.cpp:83
float gsl_determinant(const gsl_matrix *m)
Definition: helpFuncs.cpp:143
unsigned int computeRotationMatrixWahba(const std::vector< Point3D > &unrotated, const std::vector< Point3D > &rotated, const std::vector< float > &weights, gsl_matrix *&R)
Given a series of paired observations of directions, compute the least squares rotation matrix betwee...
Definition: rotations.cpp:147
gsl_matrix * getRotationMatrix(const Point3D &pt, float theta)
Definition: rotations.cpp:246
Point3D crossProd(const Point3D &pt) const
Calculate the cross product of this and another point.
Definition: point3D.cpp:285
void gsl_matrix_mult(const gsl_matrix *A, const gsl_matrix *B, gsl_matrix *&res, bool alloc=false)
Definition: misc.cpp:24
double gsl_matrix_det(gsl_matrix *A, bool inPlace=false)
Definition: rotations.cpp:37
#define TEST(f, g)
Definition: aptAssert.h:49
float sqrMag() const
Returns magnitude^2, taking this as a position vector.
Definition: point3D.cpp:332
A 3D point data class storage.
Definition: point3D.h:39
void transform3x3(const gsl_matrix *matrix)
Perform a 3x3 matrix transformation using a GSL matrix.
Definition: point3D.cpp:445
void normalise()
Make point unit magnitude, maintaining direction.
Definition: point3D.cpp:337
#define ASSERT(f)
void quat_rot(Point3D &p, const Point3D &r, float angle)
Rotate a point around a given rotation axis by a specified angle.
Definition: quat.cpp:59