END_TEST

START_TEST(test_vector_dot) {
  u32 i, t;

  seed_rng();
  for (t = 0; t < LINALG_NUM; t++) {
    u32 n = sizerand(MSIZE_MAX);
    u32 mid;
    if (n % 2 == 0)
      mid = n / 2;
    else
      mid = (n - 1) / 2;

    double A[n], B[n];
    for (i = 0; i < n; i++) {
      A[i] = mrand/1e20;
      if (i < mid)
        B[n-i-1] = -A[i];
      else
        B[n-i-1] = A[i];
    }
    double dot = vector_dot(n, A, B);
    if (n % 2 == 0)
      fail_unless(fabs(dot) < LINALG_TOL,
                  "Dot product differs from zero: %lf", vector_dot(n, A, B));
    else
      fail_unless(fabs(dot - A[mid]*B[mid]) < LINALG_TOL,
                  "Dot product differs from square of middle element "
                  "%lf: %lf (%lf)",
                  A[mid]*B[mid], dot, dot - A[mid]*B[mid]);
  }
}
void setup()
{
  /* Seed the random number generator with a specific seed for our test. */
  srandom(1);

  /* Create a new pool and fill it with a sequence of ints. */
  test_pool_seq = memory_pool_new(50, sizeof(s32));

  s32 *x;
  for (u32 i=0; i<22; i++) {
    x = (s32 *)memory_pool_add(test_pool_seq);
    fail_unless(x != 0, "Null pointer returned by memory_pool_add");
    *x = i;
  }
  /* Create a new pool and fill it entirely with random numbers. */
  test_pool_random = memory_pool_new(20, sizeof(s32));

  for (u32 i=0; i<20; i++) {
    x = (s32 *)memory_pool_add(test_pool_random);
    fail_unless(x != 0, "Null pointer returned by memory_pool_add");
    *x = sizerand(16);
  }

  /* Create a new pool and leave it empty. */
  test_pool_empty = memory_pool_new(50, sizeof(s32));
}
END_TEST

START_TEST(test_matrix_add_sc) {
  u32 i, j, t;

  seed_rng();
  for (t = 0; t < LINALG_NUM; t++) {
    u32 n = sizerand(MSIZE_MAX);
    u32 m = sizerand(MSIZE_MAX);
    double A[n*m];
    double B[m*n];
    for (i = 0; i < n; i++)
      for (j = 0; j < m; j++) {
        A[m*i + j] = mrand;
      }
    matrix_add_sc(n, m, A, A, -1, B);
    for (i = 0; i < n; i++)
      for (j = 0; j < m; j++)
        fail_unless(fabs(B[m*i + j]) < LINALG_TOL,
                    "Matrix differs from zero: %lf", B[m*i + j]);
  }
}
END_TEST

START_TEST(test_matrix_udu_2)
{
  u32 n = sizerand(MSIZE_MAX);
  double M[n][n];
  double M_orig[n][n];

  for (u32 i=0; i<n; i++) {
    for (u32 j=0; j<=i; j++) {
      M[i][j] = M[j][i] = mrand;
    }
  }

  /* Square the random matrix to ensure it is positive semi-definite. */
  matrix_multiply(n, n, n, (double *)M, (double *)M, (double *)M_orig);
  memcpy(M, M_orig, n * n * sizeof(double));

  double U[n][n];
  memset(U, 0, n * n * sizeof(double));
  double D[n];
  memset(D, 0, n * sizeof(double));

  matrix_udu(n, (double *)M, (double *)U, D);

  /* Check U is unit upper triangular. */
  for (u32 i=0; i<n; i++) {
    for (u32 j=0; j<n; j++) {
      if (i == j) {
        fail_unless(fabs(U[i][j] - 1) < LINALG_TOL,
          "U diagonal element != 1 (was %f)", M[i][j]);
      }
      if (i > j) {
        fail_unless(fabs(U[i][j]) < LINALG_TOL, "U lower triangle element != 0");
      }
    }
  }

  /* Check reconstructed matrix is correct. */
  double M_[n][n];
  memset(M_, 0, n * n * sizeof(double));
  matrix_reconstruct_udu(n, (double *)U, D, (double *)M_);

  for (u32 i=0; i<n; i++) {
    for (u32 j=0; j<n; j++) {
      fail_unless(fabs(M_orig[i][j] - M_[i][j]) < LINALG_TOL * MATRIX_MAX,
        "reconstructed result != original matrix, delta[%d][%d] = %f",
        i, j, fabs(M_orig[i][j] - M_[i][j]));
    }
  }
}
END_TEST

START_TEST(test_matrix_transpose) {
  u32 i, j, t;

  seed_rng();
  for (t = 0; t < LINALG_NUM; t++) {
    u32 n = sizerand(MSIZE_MAX);
    u32 m = sizerand(MSIZE_MAX);
    double A[n*m];
    double B[m*n];
    double C[n*m];
    for (i = 0; i < n; i++)
      for (j = 0; j < m; j++)
        A[m*i + j] = mrand;
    matrix_transpose(n, m, A, B);
    matrix_transpose(m, n, B, C);
    for (i = 0; i < n; i++)
      for (j = 0; j < m; j++)
        fail_unless(fabs(A[m*i + j] - C[m*i + j]) < LINALG_TOL,
                    "Matrix element differs from original: %lf, %lf",
                    A[m*i + j], C[m*i + j]);
  }
}
END_TEST

START_TEST(test_matrix_copy) {
  u32 i, j, t;
  double tmp;

  seed_rng();
  for (t = 0; t < LINALG_NUM; t++) {
    u32 n = sizerand(MSIZE_MAX);
    u32 m = sizerand(MSIZE_MAX);
    double A[n*m];
    double B[m*n];
    for (i = 0; i < n; i++)
      for (j = 0; j < m; j++)
        A[m*i + j] = mrand;
    matrix_copy(n, m, A, B);
    for (i = 0; i < n; i++)
      for (j = 0; j < m; j++) {
        tmp = fabs(B[m*i + j] - A[m*i + j]);
        fail_unless(tmp < LINALG_TOL,
                    "Matrix differs from zero: %lf", tmp);
      }
  }
}
END_TEST

START_TEST(test_vector_add_sc) {
  u32 i, t;

  seed_rng();
  for (t = 0; t < LINALG_NUM; t++) {
    u32 n = sizerand(MSIZE_MAX);
    double A[n], B[n];
    for (i = 0; i < n; i++)
      A[i] = mrand;
    vector_add_sc(n, A, A, -1, B);
    for (i = 0; i < n; i++)
      fail_unless(fabs(B[i]) < LINALG_TOL,
                  "Vector element differs from 0: %lf",
                  B[i]);
  }
}
END_TEST

START_TEST(test_vector_normalize) {
  u32 i, t;

  seed_rng();
  for (t = 0; t < LINALG_NUM; t++) {
    u32 n = sizerand(MSIZE_MAX);
    double A[n];
    for (i = 0; i < n; i++)
      A[i] = mrand;
    vector_normalize(n, A);
    double vnorm = vector_norm(n, A);
    fail_unless(fabs(vnorm - 1) < LINALG_TOL,
                "Norm differs from 1: %lf",
                vector_norm(n, A));
  }
}
END_TEST

START_TEST(test_vector_norm) {
  u32 i, t;

  seed_rng();
  for (t = 0; t < LINALG_NUM; t++) {
    u32 n = sizerand(MSIZE_MAX);
    double test = mrand/1e22;
    double A[n];
    for (i = 0; i < n; i++)
      A[i] = test;
    fail_unless(fabs(vector_norm(n, A)*vector_norm(n, A) - n * test * test)
                < LINALG_TOL * vector_norm(n, A),
                "Norm differs from expected %lf: %lf (%lf)",
                n * test * test, vector_norm(n, A) * vector_norm(n, A),
                fabs(vector_norm(n, A) * vector_norm(n, A) - n * test * test));
  }
}
END_TEST

START_TEST(test_vector_subtract) {
  u32 i, t;

  seed_rng();
  for (t = 0; t < LINALG_NUM; t++) {
    u32 n = sizerand(MSIZE_MAX);
    double A[n], B[n], C[n];
    for (i = 0; i < n; i++) {
      A[i] = mrand;
      B[i] = A[i];
    }
    vector_subtract(n, A, B, C);
    for (i = 0; i < n; i++)
      fail_unless(fabs(C[i]) < LINALG_TOL,
                  "Vector element differs from 0: %lf",
                  C[i]);
  }
}
END_TEST

START_TEST(test_vector_mean) {
  u32 i, t;

  seed_rng();
  for (t = 0; t < LINALG_NUM; t++) {
    u32 n = sizerand(MSIZE_MAX);
    double A[n];
    double test = mrand/1e22;
    for (i = 0; i < n; i++)
      A[i] = test + i;
    double mean = vector_mean(n, A);
    double expect = test + (n - 1.0) / 2.0;
    fail_unless(fabs(mean - expect)
                < LINALG_TOL,
                "Mean differs from expected %lf: %lf (%lf)",
                expect, mean, fabs(mean - expect));
  }
}