package org.biojava.nbio.structure.geometry; import static org.junit.Assert.*; import java.io.IOException; import javax.vecmath.AxisAngle4d; import javax.vecmath.Matrix4d; import javax.vecmath.Point3d; import javax.vecmath.Quat4d; import javax.vecmath.Vector3d; import org.biojava.nbio.structure.Calc; import org.biojava.nbio.structure.Structure; import org.biojava.nbio.structure.StructureException; import org.biojava.nbio.structure.StructureIO; import org.biojava.nbio.structure.StructureTools; import org.biojava.nbio.structure.geometry.UnitQuaternions; import org.junit.Test; /** * Test the methods in the {@link UnitQuaternions} class. * * @author Aleix Lafita * @since 5.0.0 * */ public class TestUnitQuaternions { /** * Test {@link UnitQuaternions#orientation(javax.vecmath.Point3d[])}. * <p> * Tests the identity orientation, orientation around one coordinate axis * and orientation around a non-coordinate axis. * * @throws StructureException * @throws IOException */ @Test public void testOrientation() throws IOException, StructureException { // Get points from a structure. It is difficult to generate points // with no bias in their distribution (too uniform, ie). Structure pdb = StructureIO.getStructure("4hhb.A"); Point3d[] cloud = Calc.atomsToPoints(StructureTools .getRepresentativeAtomArray(pdb)); // Center the cloud at the origin CalcPoint.center(cloud); // Orient its principal axes to the coordinate axis Quat4d orientation = UnitQuaternions.orientation(cloud); Matrix4d transform = new Matrix4d(); transform.set(orientation); transform.invert(); CalcPoint.transform(transform, cloud); // The orientation found now should be 0 (it has been re-oriented) orientation = UnitQuaternions.orientation(cloud); AxisAngle4d axis = new AxisAngle4d(); axis.set(orientation); // No significant rotation assertEquals(orientation.x, 0.0, 0.01); assertEquals(orientation.y, 0.0, 0.01); assertEquals(orientation.z, 0.0, 0.01); assertEquals(axis.angle, 0.0, 0.01); // Now try to recover an orientation Quat4d quat = new Quat4d(0.418, 0.606, 0.303, 0.606); Matrix4d mat = new Matrix4d(); mat.set(quat); CalcPoint.transform(mat, cloud); orientation = UnitQuaternions.orientation(cloud); // Test recovering the quaternion (q and -q same rotation) assertEquals(Math.abs(orientation.x), quat.x, 0.01); assertEquals(Math.abs(orientation.y), quat.y, 0.01); assertEquals(Math.abs(orientation.z), quat.z, 0.01); assertEquals(Math.abs(orientation.w), quat.w, 0.01); } /** * Test {@link UnitQuaternions#orientationMetric(Point3d[], Point3d[])}. * <p> * Tests the range of values of the metric with a perfect correlation, * perfect anticorrelation and intermediate values. */ @Test public void testOrientationMetricRange() { // no rotation quaternion Quat4d qa = new Quat4d(0, 0, 0, 1); Quat4d qb = new Quat4d(qa); // Two equal quaternions produce the minimum score of 0 assertEquals(UnitQuaternions.orientationMetric(qa, qb), 0, 0.01); // 90 degrees rotation over x qa = new Quat4d(0.707, 0, 0, 0.707); // 270 degrees rotation over x qb = new Quat4d(0.707, 0, 0, -0.707); // two quaternions with 180 degree axis produce the max score Pi / 2 assertEquals(UnitQuaternions.orientationMetric(qa, qb), Math.PI / 2, 0.01); // 90 degrees rotation over y qb = new Quat4d(0, 0.707, 0, 0.707); // two quaternions with 90 degree axis produce the score Pi / 4 assertEquals(UnitQuaternions.orientationMetric(qa, qb), Math.PI / 3, 0.01); // two quaternions with 45 degree axis produce the score Pi / 8 qb = new Quat4d(0.383, 0, 0, 0.924); assertEquals(UnitQuaternions.orientationMetric(qa, qb), Math.PI / 8, 0.01); // 90 degrees rotation over x in negative qb = new Quat4d(0, -0.707, 0, -0.707); // assert no negative angles are returned assertEquals(UnitQuaternions.orientationMetric(qa, qb), Math.PI / 3, 0.01); } /** * Test {@link UnitQuaternions#orientationMetric(Point3d[], Point3d[])} on a * real structure, which will be deviating a little bit every time. * * @throws StructureException * @throws IOException */ @Test public void testOrientationMetricIncrement() throws IOException, StructureException { // The rotation increment will be Pi/10, Pi/15 and Pi/12 degrees in X,Y // and Z Matrix4d transform = new Matrix4d(); transform.rotX(Math.PI / 10); transform.rotY(Math.PI / 12); transform.rotZ(Math.PI / 15); // Get points from a structure. Structure pdb = StructureIO.getStructure("4hhb.A"); Point3d[] cloud = Calc.atomsToPoints(StructureTools .getRepresentativeAtomArray(pdb)); Point3d[] cloud2 = CalcPoint.clonePoint3dArray(cloud); // Center the clouds at the origin CalcPoint.center(cloud); CalcPoint.center(cloud2); // Their orientation is equal at this stage double m0 = UnitQuaternions.orientationMetric(cloud, cloud2); assertEquals(m0, 0.0, 0.001); // Assert it keeps incrementing every time transform is applied CalcPoint.transform(transform, cloud2); double m1 = UnitQuaternions.orientationMetric(cloud, cloud2); assertTrue(m1 > m0); CalcPoint.transform(transform, cloud2); double m2 = UnitQuaternions.orientationMetric(cloud, cloud2); assertTrue(m2 > m1); CalcPoint.transform(transform, cloud2); double m3 = UnitQuaternions.orientationMetric(cloud, cloud2); assertTrue(m3 > m2); CalcPoint.transform(transform, cloud2); double m4 = UnitQuaternions.orientationMetric(cloud, cloud2); assertTrue(m4 > m3); CalcPoint.transform(transform, cloud2); double m5 = UnitQuaternions.orientationMetric(cloud, cloud2); assertTrue(m5 > m4); CalcPoint.transform(transform, cloud2); double m6 = UnitQuaternions.orientationMetric(cloud, cloud2); assertTrue(m6 > m5); CalcPoint.transform(transform, cloud2); double m7 = UnitQuaternions.orientationMetric(cloud, cloud2); assertTrue(m7 > m6); CalcPoint.transform(transform, cloud2); double m8 = UnitQuaternions.orientationMetric(cloud, cloud2); assertTrue(m8 > m7); CalcPoint.transform(transform, cloud2); double m9 = UnitQuaternions.orientationMetric(cloud, cloud2); assertTrue(m9 > m8); } /** * Test {@link UnitQuaternions#relativeOrientation(Point3d[], Point3d[])} on * a real structure. Test recovering of the angle applied. * * @throws StructureException * @throws IOException */ @Test public void testRelativeOrientation() throws IOException, StructureException { // Get points from a structure. Structure pdb = StructureIO.getStructure("4hhb.A"); Point3d[] cloud = Calc.atomsToPoints(StructureTools .getRepresentativeAtomArray(pdb)); Point3d[] cloud2 = CalcPoint.clonePoint3dArray(cloud); // Test orientation angle equal to 0 at this point double angle = UnitQuaternions.orientationAngle(cloud, cloud2, false); assertEquals(angle, 0, 0.001); // Apply a 30 degree rotation to cloud AxisAngle4d axis = new AxisAngle4d(new Vector3d(1,1,1), Math.PI / 6); Matrix4d transform = new Matrix4d(); transform.set(axis); CalcPoint.transform(transform, cloud); angle = UnitQuaternions.orientationAngle(cloud, cloud2, false); angle = Math.min(Math.abs(2 * Math.PI - angle), angle); // Test that angle was recovered assertEquals(angle, Math.PI / 6, 0.001); } }