/* nag_check_derivs (c05zdc) Example Program.
 *
 * NAGPRODCODE Version.
 *
 * Copyright 2016 Numerical Algorithms Group.
 *
 * Mark 26, 2016.
 */

#include <nag.h>
#include <stdio.h>
#include <nag_stdlib.h>
#include <nagc05.h>

#ifdef __cplusplus
extern "C"
{
#endif
  static void NAG_CALL f(Integer m, Integer n, double x[], double fvec[],
                         double fjac[], Integer iflag);
#ifdef __cplusplus
}
#endif

int main(void)
{
  Integer exit_status = 0, j, m, n, mode, iflag, err_detected;
  NagError fail;
  double *fjac = 0, *fvec = 0, *x = 0, *xp = 0, *fvecp = 0, *err = 0;
  INIT_FAIL(fail);

  printf("nag_check_derivs (c05zdc) Example Program Results\n");
  n = 3;
  m = n;

  if (n > 0) {
    if (!(fjac = NAG_ALLOC(m * n, double)) ||
        !(fvec = NAG_ALLOC(m, double)) ||
        !(fvecp = NAG_ALLOC(m, double)) ||
        !(err = NAG_ALLOC(m, double)) ||
        !(x = NAG_ALLOC(n, double)) || !(xp = NAG_ALLOC(n, double)))
    {
      printf("Allocation failure\n");
      exit_status = -1;
      goto END;
    }
  }
  else {
    printf("Invalid n.\n");
    exit_status = 1;
    goto END;
  }

  /* Set up an arbitrary point at which to check the 1st derivatives */
  x[0] = 9.2e-01;
  x[1] = 1.3e-01;
  x[2] = 5.4e-01;

  /* nag_check_derivs (c05zdc).
   * Derivative checker for user-supplied Jacobian
   */

  mode = 1;
  nag_check_derivs(mode, m, n, x, fvec, fjac, xp, fvecp, err, &fail);

  if (fail.code != NE_NOERROR) {
    printf("Error from nag_check_derivs (c05zdc).\n%s\n", fail.message);
    exit_status = 1;
    goto END;
  }

  /* Evaluate at the original point x and the update point xp */
  /* Get fvec, the functions at x */
  iflag = 1;
  f(m, n, x, fvec, fjac, iflag);

  /* Get fvecp, the functions at xp */
  iflag = 1;
  f(m, n, xp, fvecp, fjac, iflag);

  /* Get fjac, the Jacobian at x */
  iflag = 2;
  f(m, n, x, fvec, fjac, iflag);

  mode = 2;
  nag_check_derivs(mode, m, n, x, fvec, fjac, xp, fvecp, err, &fail);

  if (fail.code != NE_NOERROR) {
    printf("Error from nag_check_derivs (c05zdc).\n%s\n", fail.message);
    exit_status = 1;
    goto END;
  }

  printf("\nAt point ");
  for (j = 0; j < n; ++j)
    printf("%13.5e", x[j]);
  printf(",\n");

  err_detected = 0;

  for (j = 0; j < n; ++j) {

    if (err[j] <= 0.5) {
      printf("suspicious gradient number %" NAG_IFMT
             " with error measure %13.5e\n", j, err[j]);
      err_detected = 1;
    }

  }

  if (!err_detected) {
    printf("gradients appear correct\n");
  }

END:
  NAG_FREE(fjac);
  NAG_FREE(fvec);
  NAG_FREE(fvecp);
  NAG_FREE(err);
  NAG_FREE(x);
  NAG_FREE(xp);
  return exit_status;
}

static void NAG_CALL f(Integer m, Integer n, double x[], double fvec[],
                       double fjac[], Integer iflag)
{
  Integer j, k;

  if (iflag == 1) {
    /* Calculate the function values */
    for (k = 0; k < m; k++) {
      fvec[k] = (3.0 - x[k] * 2.0) * x[k] + 1.0;
      if (k > 0)
        fvec[k] -= x[k - 1];
      if (k < m - 1)
        fvec[k] -= x[k + 1] * 2.0;
    }
  }
  else if (iflag == 2) {
    /* Calculate the corresponding first derivatives */
    for (k = 0; k < m; k++) {
      for (j = 0; j < n; j++)
        fjac[j * m + k] = 0.0;
      fjac[k * m + k] = 3.0 - x[k] * 4.0;
      if (k > 0)
        fjac[(k - 1) * m + k] = -1.0;
      if (k < m - 1)
        fjac[(k + 1) * m + k] = -2.0;
    }
  }
}