--- /dev/null
+package jalview.math;
+
+import static org.testng.Assert.assertEquals;
+
+import jalview.math.RotatableMatrix.Axis;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class RotatableMatrixTest
+{
+ private RotatableMatrix rm;
+
+ @BeforeMethod(alwaysRun = true)
+ public void setUp()
+ {
+ rm = new RotatableMatrix();
+
+ /*
+ * 0.5 1.0 1.5
+ * 1.0 2.0 3.0
+ * 1.5 3.0 4.5
+ */
+ for (int i = 1; i <= 3; i++)
+ {
+ for (int j = 1; j <= 3; j++)
+ {
+ rm.setValue(i - 1, j - 1, i * j / 2f);
+ }
+ }
+ }
+
+ @Test(groups = "Functional")
+ public void testPreMultiply()
+ {
+ float[][] pre = new float[3][3];
+ int i = 1;
+ for (int j = 0; j < 3; j++)
+ {
+ for (int k = 0; k < 3; k++)
+ {
+ pre[j][k] = i++;
+ }
+ }
+
+ rm.preMultiply(pre);
+
+ /*
+ * check rm[i, j] is now the product of the i'th row of pre
+ * and the j'th column of (original) rm
+ */
+ for (int j = 0; j < 3; j++)
+ {
+ for (int k = 0; k < 3; k++)
+ {
+ float expected = 0f;
+ for (int l = 0; l < 3; l++)
+ {
+ float rm_l_k = (l + 1) * (k + 1) / 2f;
+ expected += pre[j][l] * rm_l_k;
+ }
+ assertEquals(rm.getValue(j, k), expected,
+ String.format("[%d, %d]", j, k));
+ }
+ }
+ }
+
+ @Test(groups = "Functional")
+ public void testVectorMultiply()
+ {
+ float[] result = rm.vectorMultiply(new float[] { 2f, 3f, 4.5f });
+
+ // vector times first column of matrix
+ assertEquals(result[0], 2f * 0.5f + 3f * 1f + 4.5f * 1.5f);
+
+ // vector times second column of matrix
+ assertEquals(result[1], 2f * 1.0f + 3f * 2f + 4.5f * 3f);
+
+ // vector times third column of matrix
+ assertEquals(result[2], 2f * 1.5f + 3f * 3f + 4.5f * 4.5f);
+ }
+
+ @Test(groups = "Functional")
+ public void testGetRotation()
+ {
+ float theta = 60f;
+ double cosTheta = Math.cos((theta * Math.PI / 180f));
+ double sinTheta = Math.sin((theta * Math.PI / 180f));
+
+ /*
+ * sanity check that sin(60) = sqrt(3) / 2, cos(60) = 1/2
+ */
+ double delta = 0.0001d;
+ assertEquals(cosTheta, 0.5f, delta);
+ assertEquals(sinTheta, Math.sqrt(3d) / 2d, delta);
+
+ /*
+ * so far so good, now verify rotations
+ * @see https://en.wikipedia.org/wiki/Rotation_matrix#Basic_rotations
+ */
+
+ /*
+ * 60 degrees about X axis should be
+ * 1 0 0
+ * 0 cos -sin
+ * 0 sin cos
+ * but code applies the negative of this
+ * nb cos(-x) = cos(x), sin(-x) = -sin(x)
+ */
+ float[][] rot = RotatableMatrix.getRotation(theta, Axis.X);
+ assertEquals(rot[0][0], 1f, delta);
+ assertEquals(rot[0][1], 0f, delta);
+ assertEquals(rot[0][2], 0f, delta);
+ assertEquals(rot[1][0], 0f, delta);
+ assertEquals(rot[1][1], cosTheta, delta);
+ assertEquals(rot[1][2], sinTheta, delta);
+ assertEquals(rot[2][0], 0f, delta);
+ assertEquals(rot[2][1], -sinTheta, delta);
+ assertEquals(rot[2][2], cosTheta, delta);
+
+ /*
+ * 60 degrees about Y axis should be
+ * cos 0 sin
+ * 0 1 0
+ * -sin 0 cos
+ * but code applies the negative of this
+ */
+ rot = RotatableMatrix.getRotation(theta, Axis.Y);
+ assertEquals(rot[0][0], cosTheta, delta);
+ assertEquals(rot[0][1], 0f, delta);
+ assertEquals(rot[0][2], -sinTheta, delta);
+ assertEquals(rot[1][0], 0f, delta);
+ assertEquals(rot[1][1], 1f, delta);
+ assertEquals(rot[1][2], 0f, delta);
+ assertEquals(rot[2][0], sinTheta, delta);
+ assertEquals(rot[2][1], 0f, delta);
+ assertEquals(rot[2][2], cosTheta, delta);
+
+ /*
+ * 60 degrees about Z axis should be
+ * cos -sin 0
+ * sin cos 0
+ * 0 0 1
+ * - and it is!
+ */
+ rot = RotatableMatrix.getRotation(theta, Axis.Z);
+ assertEquals(rot[0][0], cosTheta, delta);
+ assertEquals(rot[0][1], -sinTheta, delta);
+ assertEquals(rot[0][2], 0f, delta);
+ assertEquals(rot[1][0], sinTheta, delta);
+ assertEquals(rot[1][1], cosTheta, delta);
+ assertEquals(rot[1][2], 0f, delta);
+ assertEquals(rot[2][0], 0f, delta);
+ assertEquals(rot[2][1], 0f, delta);
+ assertEquals(rot[2][2], 1f, delta);
+ }
+}