-
Harry Jeffery authored
Refs #10021
Harry Jeffery authoredRefs #10021
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
QuatTest.h 14.83 KiB
#ifndef MANTID_TESTQUAT__
#define MANTID_TESTQUAT__
#include <cxxtest/TestSuite.h>
#include <cmath>
#include <ostream>
#include <float.h>
#include "MantidKernel/V3D.h"
#include "MantidKernel/Quat.h"
#include "MantidKernel/Matrix.h"
using namespace Mantid::Kernel;
class QuatTest : public CxxTest::TestSuite
{
private:
Mantid::Kernel::Quat q,p;
public:
void testoperatorbracket()
{
p[0]=0;
p[1]=1;
p[2]=2;
p[3]=3;
TS_ASSERT_EQUALS(p[0],0.0);
TS_ASSERT_EQUALS(p[1],1.0);
TS_ASSERT_EQUALS(p[2],2.0);
TS_ASSERT_EQUALS(p[3],3.0);
}
void testEmptyConstructor()
{
TS_ASSERT_EQUALS(q[0],1.0);
TS_ASSERT_EQUALS(q[1],0.0);
TS_ASSERT_EQUALS(q[2],0.0);
TS_ASSERT_EQUALS(q[3],0.0);
}
void testValueConstructor()
{
Mantid::Kernel::Quat q1(1,2,3,4);
TS_ASSERT_EQUALS(q1[0],1.0);
TS_ASSERT_EQUALS(q1[1],2.0);
TS_ASSERT_EQUALS(q1[2],3.0);
TS_ASSERT_EQUALS(q1[3],4.0);
}
void testAngleAxisConstructor()
{
Mantid::Kernel::V3D v(1,1,1);
// Construct quaternion to represent rotation
// of 45 degrees around the 111 axis.
Mantid::Kernel::Quat q1(90.0,v);
double c=1.0/sqrt(2.0);
double s=c/sqrt(3.0);
TS_ASSERT_DELTA(q1[0],c,0.000001);
TS_ASSERT_DELTA(q1[1],s,0.000001);
TS_ASSERT_DELTA(q1[2],s,0.000001);
TS_ASSERT_DELTA(q1[3],s,0.000001);
}
void testoperatorassignmentfromdouble()
{
q(2,3,4,5);
TS_ASSERT_EQUALS(q[0],2.0);
TS_ASSERT_EQUALS(q[1],3.0);
TS_ASSERT_EQUALS(q[2],4.0);
TS_ASSERT_EQUALS(q[3],5.0);
}
void testoperatorassignmentfromangleaxis()
{
Mantid::Kernel::V3D v(1,1,1);
q(90.0,v);
double c=1.0/sqrt(2.0);
double s=c/sqrt(3.0);
TS_ASSERT_DELTA(q[0],c,0.000001);
TS_ASSERT_DELTA(q[1],s,0.000001);
TS_ASSERT_DELTA(q[2],s,0.000001);
TS_ASSERT_DELTA(q[3],s,0.000001);
//Now rotate 45 degrees around y
q(45, V3D(0,1,0));
V3D X(1,0,0);
q.rotate(X);
double a = sqrt(2.0)/2;
TS_ASSERT(X==V3D(a,0,-a));
//Now rotate -45 degrees around y
q(-45, V3D(0,1,0));
X(1,0,0);
q.rotate(X);
TS_ASSERT(X==V3D(a,0,a));
}
void testoperatorequal()
{
q=p;
TS_ASSERT_EQUALS(q[0],p[0]);
TS_ASSERT_EQUALS(q[1],p[1]);
TS_ASSERT_EQUALS(q[2],p[2]);
TS_ASSERT_EQUALS(q[3],p[3]);
}
void testlenmethod()
{
q(1,2,3,4);
TS_ASSERT_EQUALS(q.len(),sqrt(30.0));
}
void testlen2method()
{
q(1,2,3,4);
TS_ASSERT_EQUALS(q.len2(),30.0);
}
void testinitmehtod()
{
q.init();
TS_ASSERT_EQUALS(q[0],1);
TS_ASSERT_EQUALS(q[1],0);
TS_ASSERT_EQUALS(q[2],0);
TS_ASSERT_EQUALS(q[3],0);
}
void testnormalizemethod()
{
q(2,2,2,2);
q.normalize();
TS_ASSERT_DELTA(q[0],0.5,0.000001);
TS_ASSERT_DELTA(q[1],0.5,0.000001);
TS_ASSERT_DELTA(q[2],0.5,0.000001);
TS_ASSERT_DELTA(q[3],0.5,0.000001);
}
void testconjugatemethod()
{
q(1,1,1,1);
q.conjugate();
TS_ASSERT_EQUALS(q[0],1);
TS_ASSERT_EQUALS(q[1],-1);
TS_ASSERT_EQUALS(q[2],-1);
TS_ASSERT_EQUALS(q[3],-1);
}
void testinversemethod()
{
q(2,3,4,5);
Mantid::Kernel::Quat qinv(q);
qinv.inverse();
q*=qinv;
TS_ASSERT_DELTA(q[0],1,0.000001);
TS_ASSERT_DELTA(q[1],0,0.000001);
TS_ASSERT_DELTA(q[2],0,0.000001);
TS_ASSERT_DELTA(q[3],0,0.000001);
}
void testoperatorplus()
{
q(1,1,1,1);
p(-1,2,1,3);
Mantid::Kernel::Quat res;
res=p+q;
TS_ASSERT_EQUALS(res[0],0);
TS_ASSERT_EQUALS(res[1],3);
TS_ASSERT_EQUALS(res[2],2);
TS_ASSERT_EQUALS(res[3],4);
}
void testoperatorminus()
{
q(1,1,1,1);
p(-1,2,1,3);
Mantid::Kernel::Quat res;
res=p-q;
TS_ASSERT_EQUALS(res[0],-2);
TS_ASSERT_EQUALS(res[1],1);
TS_ASSERT_EQUALS(res[2],0);
TS_ASSERT_EQUALS(res[3],2);
}
void testoperatortimes()
{
q(1,1,1,1);
p(-1,2,1,3);
Mantid::Kernel::Quat res;
res=p*q;
TS_ASSERT_EQUALS(res[0],-7);
TS_ASSERT_EQUALS(res[1],-1);
TS_ASSERT_EQUALS(res[2],1);
TS_ASSERT_EQUALS(res[3],3);
}
void testoperatordoublequal()
{
p=q;
TS_ASSERT(p==q);
q(1,4,5,6);
TS_ASSERT(p!=q);
}
void testoperatornotequal()
{
q(1,2,3,4);
TS_ASSERT(p!=q);
p=q;
TS_ASSERT(!(p!=q));
}
void testRotateVector()
{
double a = sqrt(2.0)/2;
//Trivial
p(1,0,0,0);//Identity quaternion
V3D v(1,0,0);
V3D orig_v = v;
p.rotate(v);
TS_ASSERT(orig_v==v);
//Now do more angles
v = V3D(1,0,0);
p(90., V3D(0,1,0)); //90 degrees, right-handed, around y
p.rotate(v);
TS_ASSERT(v==V3D(0,0,-1));
v = V3D(1,0,0);
p(45., V3D(0,0,1));
p.rotate(v);
TS_ASSERT(v==V3D(a, a, 0));
v = V3D(1,0,0);
p(-45., V3D(0,0,1));
p.rotate(v);
TS_ASSERT(v==V3D(a, -a, 0));
v = V3D(1,0,0);
p(30., V3D(0,0,1));
p.rotate(v);
TS_ASSERT(v==V3D(sqrt(3.0)/2, 0.5, 0));
v = V3D(1,0,0);
p(125., V3D(1,0,0));
p.rotate(v);
TS_ASSERT(v==V3D(1,0,0));
//90 deg around +Z
p(90, V3D(0,0,1));
v = V3D(1,0,0); p.rotate(v); TS_ASSERT(v==V3D(0,1,0));
v = V3D(0,1,0); p.rotate(v); TS_ASSERT(v==V3D(-1,0,0));
//std::cout << "Rotated v is" << v << "\n";
}
void testGetRotation()
{
V3D some(1,0.5,1);
V3D target(1,2,-1);
//V3D some(1,0,0);
//V3D target(0,1,0);
V3D rotAxis = some.cross_prod(target);
rotAxis.normalize();
double targ_norm=target.norm();
double some_norm=some.norm();
double cc = some.scalar_prod(target)/some_norm/targ_norm;
double rotAngle = acos(cc)*180/M_PI;
// rotator will be unit quaternion as it is build by the constructor this way;
Quat rotator(rotAngle,rotAxis);
std::vector<double> rotMatrix;
TSM_ASSERT_THROWS_NOTHING("The rotator quaternion has to be a unit quaternion",rotMatrix= rotator.getRotation(true));
// Kroniker Deltas valid for valid rotational matrix; a_ij*a_jk=delta_jk
double cron00= rotMatrix[0]*rotMatrix[0]+rotMatrix[1]*rotMatrix[1]+rotMatrix[2]*rotMatrix[2]; TSM_ASSERT_DELTA("delta_00 should be 1",1.0,cron00,FLT_EPSILON);
double cron11= rotMatrix[3]*rotMatrix[3]+rotMatrix[4]*rotMatrix[4]+rotMatrix[5]*rotMatrix[5]; TSM_ASSERT_DELTA("delta_11 should be 1",1.0,cron11,FLT_EPSILON);
double cron22= rotMatrix[6]*rotMatrix[6]+rotMatrix[7]*rotMatrix[7]+rotMatrix[8]*rotMatrix[8]; TSM_ASSERT_DELTA("delta_22 should be 1",1.0,cron22,FLT_EPSILON);
double cron01= rotMatrix[0]*rotMatrix[1]+rotMatrix[3]*rotMatrix[4]+rotMatrix[6]*rotMatrix[7]; TSM_ASSERT_DELTA("delta_01 should be 0",0.0,cron01,FLT_EPSILON);
double cron02= rotMatrix[0]*rotMatrix[2]+rotMatrix[3]*rotMatrix[5]+rotMatrix[6]*rotMatrix[8]; TSM_ASSERT_DELTA("delta_02 should be 0",0.0,cron02,FLT_EPSILON);
double cron12= rotMatrix[1]*rotMatrix[2]+rotMatrix[4]*rotMatrix[5]+rotMatrix[7]*rotMatrix[8]; TSM_ASSERT_DELTA("delta_12 should be 0",0.0,cron12,FLT_EPSILON);
double det = rotMatrix[0]*(rotMatrix[4]*rotMatrix[8]-rotMatrix[5]*rotMatrix[7])
+ rotMatrix[1]*(rotMatrix[5]*rotMatrix[6]-rotMatrix[3]*rotMatrix[8])
+ rotMatrix[2]*(rotMatrix[3]*rotMatrix[7]-rotMatrix[4]*rotMatrix[6]);
TSM_ASSERT_DELTA("Determinant for the proper rotation matrix has to be equal to 1 ",1.0,det,FLT_EPSILON);
double x1=(rotMatrix[0]*some.X()+rotMatrix[1]*some.Y()+rotMatrix[2]*some.Z())*targ_norm/some_norm;
TSM_ASSERT_DELTA("X -coordinate obtained using the rotation matxis have to coinside with the one obtained by rotation via quat",x1,target.X(),FLT_EPSILON);
double y1=(rotMatrix[3]*some.X()+rotMatrix[4]*some.Y()+rotMatrix[5]*some.Z())*targ_norm/some_norm;
TSM_ASSERT_DELTA("Y -coordinate obtained using the rotation matxis have to coinside with the one obtained by rotation via quat",y1,target.Y(),FLT_EPSILON);
double z1=(rotMatrix[6]*some.X()+rotMatrix[7]*some.Y()+rotMatrix[8]*some.Z())*targ_norm/some_norm;
TSM_ASSERT_DELTA("Z -coordinate obtained using the rotation matxis have to coinside with the one obtained by rotation via quat",z1,target.Z(),FLT_EPSILON);
// if the vectors are not notmalized (not equal), the angle between the vectors calculated by the constructor below would not be equal to the one, calculated
// above.
some *=(targ_norm/some_norm);
Quat rot2(some,target);
std::vector<double> rotMatrix2;
TSM_ASSERT_THROWS_NOTHING("The rotator quaternion has to be a unit quaternion",rotMatrix2 =rot2.getRotation(true));
for(int i=0;i<9;i++){
TSM_ASSERT_DELTA("Elements of the rotation matrix obtained quat on 2 vectors have to be equivalent",rotMatrix[i],rotMatrix2[i],FLT_EPSILON);
}
x1=(rotMatrix2[0]*some.X()+rotMatrix2[1]*some.Y()+rotMatrix2[2]*some.Z());
TSM_ASSERT_DELTA("X -coordinate obtained using the rotation matxis have to coinside with the one obtained by rotation via quat",x1,target.X(),FLT_EPSILON);
y1=(rotMatrix2[3]*some.X()+rotMatrix2[4]*some.Y()+rotMatrix2[5]*some.Z());
TSM_ASSERT_DELTA("Y -coordinate obtained using the rotation matxis have to coinside with the one obtained by rotation via quat",y1,target.Y(),FLT_EPSILON);
z1=(rotMatrix2[6]*some.X()+rotMatrix2[7]*some.Y()+rotMatrix2[8]*some.Z());
TSM_ASSERT_DELTA("Z -coordinate obtained using the rotation matxis have to coinside with the one obtained by rotation via quat",z1,target.Z(),FLT_EPSILON);
}
void testUnitQuatFromUnitRotMatrix(){
DblMatrix Rot(3,3);
Rot[0][0]=1;
Rot[1][1]=1;
Rot[2][2]=1;
Quat Test;
Test.setQuat(Rot);
std::vector<double> rez = Test.getRotation();
std::vector<double> rot = Rot.getVector();
TSM_ASSERT_EQUALS("This operation should return rotation matrix",rot,rez);
}
void testQuatFromRotMatrix(){
DblMatrix Rot(3,3);
int Nx(5),Ny(5),Nz(3);
double Phi=M_PI/2/Nx;
double Tht=M_PI/2/Ny;
double Psi=M_PI/2/Ny;
Quat Test;
std::vector<double> rez;
std::vector<double> rot;
for(int i=0;i<=Nx;i++){
double cT=cos(Tht*i);
double sT=sin(Tht*i);
for(int j=0;j<=Ny;j++){
double cF = cos(j*Phi);
double sF = sin(j*Phi);
for (int k=0;k<=Nz;k++){
Rot.zeroMatrix();
double cP = cos(k*Psi);
double sP = sin(k*Psi);
Rot[0][0]=cT*cP; Rot[1][0]=-cF*sP+sF*sT*cP; Rot[2][0]=sF*sP+cF*sT*cP;
Rot[0][1]=cT*sP; Rot[1][1]= cF*cP+sF*sT*sP; Rot[2][1]=-sF*cP+cF*sT*sP;
Rot[0][2]=-sT; Rot[1][2]= sF*cT; Rot[2][2]= cT*cF;
Test.setQuat(Rot);
rez = Test.getRotation();
rot = Rot.getVector();
for(int ii=0;ii<9;ii++){
TSM_ASSERT_DELTA("This operation should return initial rotation matrix",rot[ii],rez[ii],1e-4);
}
}
}
}
}
void testSetFromDirectionCosineMatrix_trival()
{
Mantid::Kernel::V3D rX(1,0,0);
Mantid::Kernel::V3D rY(0,1,0);
Mantid::Kernel::V3D rZ(0,0,1);
q(rX,rY,rZ);
p(1,0,0,0); //Identity quaternion
TS_ASSERT(p==q); //Trivial rotation
}
void testSetFromDirectionCosineMatrix2()
{
//Rotate 90 deg around Y
V3D rX(0,0,-1);
V3D rY(0,1,0);
V3D rZ(1,0,0);
q(rX,rY,rZ);
p(90, V3D(0,1,0));
TS_ASSERT(p==q);
}
void testSetFromDirectionCosineMatrix2b()
{
//Rotate -45 deg around Y
double a = sqrt(2.0)/2;
V3D rX(a,0,a);
V3D rY(0,1,0);
V3D rZ(-a,0,a);
q(rX,rY,rZ);
p(-45.0, V3D(0,1,0));
TS_ASSERT(p==q);
V3D oX(1,0,0);
V3D oY(0,1,0);
V3D oZ(0,0,1);
q.rotate(oX);
q.rotate(oY);
q.rotate(oZ);
TS_ASSERT(oX==rX);
TS_ASSERT(oY==rY);
TS_ASSERT(oZ==rZ);
}
void testSetFromDirectionCosineMatrix3()
{
//Rotate 90 deg around Z
V3D rX(0,1,0);
V3D rY(-1,0,0);
V3D rZ(0,0,1);
q(rX,rY,rZ);
p(90, V3D(0,0,1));
TS_ASSERT(p==q);
}
void testSetFromDirectionCosineMatrix4()
{
//Rotate 90 deg around X
V3D rX(1,0,0);
V3D rY(0,0,1);
V3D rZ(0,-1,0);
q(rX,rY,rZ);
p(90, V3D(1,0,0));
TS_ASSERT(p==q);
}
/** Test that the rotation matrix is not transposed or otherwise funny */
void testGetRotation2()
{
V3D x(1,0,0);
Quat rot(90.0, x);
std::vector<double> matVec = rot.getRotation(true, true);
Matrix<double> mat(matVec);
V3D init(0,1,0);
V3D final = mat * init;
TS_ASSERT_DELTA(final.X(), 0.0, 1e-5);
TS_ASSERT_DELTA(final.Y(), 0.0, 1e-5);
TS_ASSERT_DELTA(final.Z(), 1.0, 1e-5);
}
void testGetEulerAngles1()
{
Quat X(120.0, V3D(1,0,0));
Quat Y(-60.0, V3D(0,1,0));
Quat Z(90.0, V3D(0,0,1));
Quat rot = X * Y * Z;
std::vector<double> angles = rot.getEulerAngles("XYZ");
TS_ASSERT_DELTA(angles[0], 120.0, 1e-5);
TS_ASSERT_DELTA(angles[1], -60.0, 1e-5);
TS_ASSERT_DELTA(angles[2], 90.0, 1e-5);
}
void testGetEulerAngles2()
{
//Test using a different convention
Quat X(0.0, V3D(1,0,0));
Quat Y(15.0, V3D(0,1,0));
Quat Z(75.0, V3D(0,0,1));
Quat rot = Z * X * Y;
std::vector<double> angles = rot.getEulerAngles("ZXY");
TS_ASSERT_DELTA(angles[0], 75.0, 1e-5);
TS_ASSERT_DELTA(angles[1], 0.0, 1e-5);
TS_ASSERT_DELTA(angles[2], 15.0, 1e-5);
}
void testGetEulerAngles3()
{
//In some cases we don't get the same angles out as we put in.
//That's okay though, so long as they represent the same rotation.
Quat X(68.0, V3D(1,0,0));
Quat Y(175.0, V3D(0,1,0));
Quat Z(20.0, V3D(0,0,1));
Quat rot = X * Y * Z;
Quat X2(-112.0, V3D(1,0,0));
Quat Y2(5.0, V3D(0,1,0));
Quat Z2(-160.0, V3D(0,0,1));
Quat rot2 = X * Y * Z;
TS_ASSERT(rot == rot2);
std::vector<double> angles = rot.getEulerAngles("XYZ");
TS_ASSERT_DELTA(angles[0], -112.0, 1e-5);
TS_ASSERT_DELTA(angles[1], 5.0, 1e-5);
TS_ASSERT_DELTA(angles[2], -160.0, 1e-5);
}
void compareArbitrary(const Quat& rotQ)
{
V3D oX(1,0,0);
V3D oY(0,1,0);
V3D oZ(0,0,1);
V3D rX = oX;
V3D rY = oY;
V3D rZ = oZ;
//Rotate the reference frame
rotQ.rotate(rX);
rotQ.rotate(rY);
rotQ.rotate(rZ);
//Now find it.
q(rX,rY,rZ);
q.rotate(oX);
q.rotate(oY);
q.rotate(oZ);
TS_ASSERT(oX==rX);
TS_ASSERT(oY==rY);
TS_ASSERT(oZ==rZ);
TS_ASSERT(rotQ==q);
// std::cout << "\nRotated coordinates are " << rX << rY << rZ << "\n";
// std::cout << "Expected (p) is" << p << "; got " << q << "\n";
// std::cout << "Re-Rotated coordinates are " << oX << oY << oZ << "\n";
}
void testSetFromDirectionCosineMatrix_arbitrary()
{
Quat rotQ;
//Try a couple of random rotations
rotQ = Quat(124.0, V3D(0.1, 0.2, sqrt(0.95)));
this->compareArbitrary(rotQ);
rotQ = Quat(-546.0, V3D(-0.5, 0.5, sqrt(0.5)));
this->compareArbitrary(rotQ);
rotQ = Quat(34.0, V3D(-0.5, 0.5, sqrt(0.5))) * Quat(-25.0, V3D(0.1, 0.2, sqrt(0.95)));
this->compareArbitrary(rotQ);
}
void testConstructorFromDirectionCosine()
{
double a = sqrt(2.0)/2;
V3D rX(a,0,a);
V3D rY(0,1,0);
V3D rZ(-a,0,a);
Quat rotQ = Quat(rX,rY,rZ);
p(-45.0, V3D(0,1,0));
TS_ASSERT(rotQ==p);
}
void test_toString()
{
Quat a(1,2,3,4);
TS_ASSERT_EQUALS( a.toString(), "[1,2,3,4]");
Quat b;
b.fromString("[4,5,6,7]");
TS_ASSERT_EQUALS( b, Quat(4,5,6,7) );
}
};
#endif