/********************************************************************** math.cpp - Unit tests for the math code in OpenBabel Copyright (C) 1998-2001 by OpenEye Scientific Software, Inc. Copyright (C) 2001-2006 Geoffrey R. Hutchison Copyright (C) 2006 Benoit Jacob This file is part of the Open Babel project. For more information, see This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ***********************************************************************/ // used to set import/export for Cygwin DLLs #ifdef WIN32 #define USING_OBDLL #endif #include #include #include #include #include #include #define REPEAT 1000 #define VERIFY(expr) \ if( expr ) verify_ok(); \ else verify_not_ok( __STRING(expr), __LINE__ ) #define TEST(func) \ strcpy( currentFunc, __STRING(func) ); \ tmp = failedCount; \ cout << "# math: entering " << currentFunc << endl; \ for( int i = 0; i < REPEAT; i++ ) func(); \ if( failedCount == tmp ) cout << "# math: passed " << currentFunc << endl; \ else cout << "# math: failed " << currentFunc << endl using namespace std; using namespace OpenBabel; OBRandom randomizer; int testCount = 0; int failedCount = 0; char currentFunc [256]; int tmp; void verify_ok() { cout << "ok " << ++testCount << "\n"; } void verify_not_ok( const char *expr, int line ) { failedCount++; cout << "not ok " << ++testCount << " - In " << currentFunc << "(), in line " << line << ", in file " << __FILE__ << ": expression ( " << expr << " ) is false.\n"; } void pickRandom( double & d ) { d = randomizer.NextFloat() * 2.0 - 1.0; } void pickRandom( vector3 & v ) { pickRandom( v.x() ); pickRandom( v.y() ); pickRandom( v.z() ); } void pickRandom( matrix3x3 & m ) { for( int row = 0; row < 3; row++ ) for( int column = 0; column < 3; column++ ) pickRandom( m( row, column ) ); } bool compare( const double & d1, const double & d2, double precision = 1e-6 ) { return IsApprox( d1, d2, precision ); } bool compare( const vector3 & v1, const vector3 & v2, double precision = 1e-6 ) { return v1.IsApprox( v2, precision ); } bool compare( const matrix3x3 & m1, const matrix3x3 & m2 ) { bool ret = true; for( int i = 0; i < 3; i++ ) ret &= compare( m1.GetColumn(i), m2.GetColumn(i) ); return ret; } // tests the constructors, accessors, mutators of class vector3. void testBasics_vector3() { double d[3]; vector3 v1, v2, v3; // test compare() itself, assuming Set() works v1.Set( 1.0, 0.0, 0.0 ); v2.Set( 1.0, 1e-20, 0.0 ); v3.Set( 1.0, 0.1, 0.0 ); VERIFY( compare( v1, v2 ) ); VERIFY( ! compare( v2, v3 ) ); // test constructors and operator= pickRandom( d[0] ); pickRandom( d[1] ); pickRandom( d[2] ); v1 = vector3( d[0], d[1], d[2] ); v2 = vector3( v1 ); v3 = v1; VERIFY( compare( v1, v2 ) ); VERIFY( compare( v1, v3 ) ); // test constant operator() VERIFY( compare( static_cast(v1).x(), d[0] ) ); VERIFY( compare( static_cast(v1).y(), d[1] ) ); VERIFY( compare( static_cast(v1).z(), d[2] ) ); // test Get v1.Get( d ); VERIFY( compare( v1.x(), d[0] ) ); VERIFY( compare( v1.y(), d[1] ) ); VERIFY( compare( v1.z(), d[2] ) ); // test non-constant operator(), Set, SetX, SetY, SetZ pickRandom( d[0] ); pickRandom( d[1] ); pickRandom( d[2] ); v1.x() = d[0]; v1.y() = d[1]; v1.z() = d[2]; VERIFY( compare( v1.x(), d[0] ) ); VERIFY( compare( v1.y(), d[1] ) ); VERIFY( compare( v1.z(), d[2] ) ); v2.Set( d ); VERIFY( compare( v1, v2 ) ); v3.SetX( d[0] ); v3.SetY( d[1] ); v3.SetZ( d[2] ); VERIFY( compare( v1, v3 ) ); } void testBasics_matrix3x3() { double d[3][3] = { { 1.0, 0.0, 0.0 }, { 0.0, 1.0, 0.0 }, { 0.0, 0.0, 1.0 } }; double x; matrix3x3 m1, m2, m3; vector3 v1, v2; int i, j; // test constructors and operator= m1 = matrix3x3(1.0); m2 = matrix3x3(d); VERIFY( compare( m1, m2 ) ); // test GetArray static double e[3][3]; // without "static", compiler optimizations can // produce bugs. pickRandom( m2 ); m2.GetArray( & e[0][0] ); m3 = matrix3x3(e); VERIFY( compare( m2, m3 ) ); // test const operator() and Get() for( i = 0; i < 3; i++ ) for( j = 0; j < 3; j++ ) { VERIFY( compare( static_cast(m2)(i,j), e[i][j] ) ); x = m2.Get( i, j ); VERIFY( compare( x, e[i][j] ) ); } // test non-const operator() and Set() for( i = 0; i < 3; i++ ) for( j = 0; j < 3; j++ ) m1(i,j) = m2(i,j); VERIFY( compare( m1, m2 ) ); for( i = 0; i < 3; i++ ) for( j = 0; j < 3; j++ ) m3.Set(i,j, m2(i,j) ); VERIFY( compare( m3, m2 ) ); // test SetColumn(), GetColumn(), SetRow(), GetRow(), transpose() for( i = 0; i < 3; i++ ) { pickRandom( v1 ); m1.SetRow( i, v1 ); v2 = m1.GetRow( i ); VERIFY( compare( v1, v2 ) ); m1.SetColumn( i, v1 ); v2 = m1.GetColumn( i ); VERIFY( compare( v1, v2 ) ); m1 = m1.transpose(); v2 = m1.GetRow( i ); VERIFY( compare( v1, v2 ) ); } } void testArithmeticOperators() { matrix3x3 mat1(1.0), mat2; vector3 vec1 = VZero, vec2; pickRandom( mat2 ); pickRandom( vec2 ); VERIFY( compare( mat2, mat1 * mat2 ) ); VERIFY( compare( mat2, mat2 * mat1 ) ); VERIFY( compare( vec2, mat1 * vec2 ) ); VERIFY( compare( vec2, vec1 + vec2 ) ); matrix3x3 mat3; pickRandom(mat3); vector3 vec3; pickRandom(vec3); pickRandom(mat1); pickRandom(vec1); VERIFY( compare( ( mat1 * mat2 ) * vec1, mat1 * ( mat2 * vec1 ) ) ); VERIFY( compare( vec1 * -3.0, - vec1 - vec1 - vec1 ) ); VERIFY( compare( 2.0 * vec1, vec1 + vec1 ) ); double a1, a2; do pickRandom(a1); while( a1 == 0.0 ); pickRandom(a2); VERIFY( compare( vec1 * ( a1 + a2 ), vec1 * a1 + vec1 * a2 ) ); VERIFY( compare( ( a1 - a2 ) * vec1, a1 * vec1 - a2 * vec1 ) ); VERIFY( compare( ( vec1 / a1 ) * a1, vec1, 1e-5 ) ); vec3 = vec1; vec3 += vec2; VERIFY( compare( vec1 + vec2, vec3 ) ); vec3 = vec1; vec3 -= vec2; VERIFY( compare( vec1 - vec2, vec3 ) ); vec3 = vec1; vec3 *= a1; VERIFY( compare( vec1 * a1, vec3 ) ); vec3 = vec1; vec3 /= a1; VERIFY( compare( vec1 / a1, vec3 ) ); } void testDistancesAnglesOrthogonality() { vector3 v1, v2, v3; do pickRandom( v1 ); while( v1.length() == 0 ); VERIFY( compare( v1.length_2(), v1.length() * v1.length() ) ); v1.createOrthoVector( v2 ); VERIFY( compare( v2.length(), 1.0 ) ); v1.normalize(); v1.createOrthoVector( v2 ); VERIFY( compare( v1.length(), 1.0 ) ); VERIFY( IsNegligible( dot( v1, v2 ), 1.0, 1e-6 ) ); matrix3x3 m1; m1.SetColumn( 0, v1 ); m1.SetColumn( 1, v2 ); m1.SetColumn( 2, cross( v1, v2 ) ); VERIFY( m1.isOrthogonal() ); pickRandom( v1 ); pickRandom( v2 ); double c = dot( v1, v2 ), s = cross( v1, v2 ).length(); VERIFY( compare( c, cos( vectorAngle( v1, v2 ) * DEG_TO_RAD ) * v1.length() * v2.length() ) ); VERIFY( compare( s, sin( vectorAngle( v1, v2 ) * DEG_TO_RAD ) * v1.length() * v2.length() ) ); VERIFY( compare( v1.distSq(v2), (v1-v2).length_2() ) ); double t; pickRandom(t); VERIFY( compare( fabs(t), Point2Plane( v1 + t * VY, v1, v1 + 5.0 * VX - 3.0 * VZ, v1 - VX + VZ ) ) ); } // Test the inversion of matrices and the matrix product. Get an // invertible random matrix, invert it, multiply and check if the // resulting matrix is the unit matrix. void testInversion() { matrix3x3 rnd; do pickRandom( rnd ); while( rnd.determinant() < 1e-3 ); matrix3x3 result = rnd * rnd.inverse(); VERIFY( result.isUnitMatrix() ); } // Test the eigenvalue finder. Set up a diagonal matrix and conjugate // by a rotation. That way we obtain a symmetric matrix that can be // diagonalized. Check if the eigenvalue finder reveals the original // diagonal elements. void testEigenvalues() { matrix3x3 Diagonal; for(int i=0; i<3; i++) for(int j=0; j<3; j++) Diagonal.Set(i, j, 0.0); Diagonal.Set(0, 0, randomizer.NextFloat()); Diagonal.Set(1, 1, Diagonal.Get(0,0)+fabs(randomizer.NextFloat())); Diagonal.Set(2, 2, Diagonal.Get(1,1)+fabs(randomizer.NextFloat())); // test the isDiagonal() method VERIFY( Diagonal.isDiagonal() ); matrix3x3 rndRotation; rndRotation.randomRotation(randomizer); // check that rndRotation is really a rotation, i.e. that randomRotation() works VERIFY( rndRotation.isOrthogonal() ); matrix3x3 toDiagonalize = rndRotation * Diagonal * rndRotation.inverse(); VERIFY( toDiagonalize.isSymmetric() ); vector3 eigenvals; toDiagonalize.findEigenvectorsIfSymmetric(eigenvals); for(unsigned int j=0; j<3; j++) VERIFY( IsNegligible( eigenvals[j] - Diagonal.Get(j,j), Diagonal.Get(2,2) ) ); VERIFY( eigenvals[0] < eigenvals[1] && eigenvals[1] < eigenvals[2] ); } // Test the eigenvector finder. Set up a symmetric diagonal matrix and // diagonalize it. The that conjugation with the computed eigenvectors // really gives a diagonal matrix. Calculate the matrix-vector // products directly to see if the vectors found are eigenvectors // indeed, and if the computed eigenvalues are correct. void testEigenvectors() { matrix3x3 rnd; pickRandom( rnd ); rnd.Set(0,1, rnd.Get(1,0)); rnd.Set(0,2, rnd.Get(2,0)); rnd.Set(1,2, rnd.Get(2,1)); vector3 eigenvals; matrix3x3 eigenvects = rnd.findEigenvectorsIfSymmetric(eigenvals); // Check if the eigenvects are normalized and mutually orthogonal VERIFY( eigenvects.isOrthogonal() ); // for an orthogonal matrix, the inverse should equal the transpose VERIFY( compare( eigenvects.inverse(), eigenvects.transpose() ) ); matrix3x3 shouldBeDiagonal = eigenvects.inverse() * rnd * eigenvects; VERIFY( shouldBeDiagonal.isDiagonal() ); for(unsigned int j=0; j<3; j++) VERIFY( compare( shouldBeDiagonal.Get(j,j), eigenvals[j] ) ); for(unsigned int i=0; i<3; i++ ) { vector3 EV = eigenvects.GetColumn(i); VERIFY( compare( rnd*EV, eigenvals[i]*EV ) ); } } int main(int argc,char *argv[]) { // turn off slow sync with C-style output (we don't use it anyway). std::ios::sync_with_stdio(false); if (argc != 1) { cout << "Usage: math" << endl; cout << " Tests OpenBabel's math code." << endl; return 0; } cout << "# math: repeating each test " << REPEAT << " times" << endl; randomizer.TimeSeed(); TEST( testBasics_vector3 ); TEST( testBasics_matrix3x3 ); TEST( testArithmeticOperators ); TEST( testDistancesAnglesOrthogonality ); TEST( testInversion ); TEST( testEigenvalues ); TEST( testEigenvectors ); cout << "1.." << testCount << endl; if( failedCount == 0 ) cout << "# math: all tests are successful" << endl; else cout << "# math: " << failedCount << " out of " << testCount << " tests failed." << endl; return 0; }