#include <petsc/private/taoimpl.h>

typedef struct {
  PetscInt n; /* Number of total variables */
  PetscInt m; /* Number of constraints */
  PetscInt nstate;
  PetscInt ndesign;
  PetscInt mx;    /* grid points in each direction */
  PetscInt ns;    /* Number of data samples (1<=ns<=8)
                  Currently only ns=1 is supported */
  PetscInt ndata; /* Number of data points per sample */
  IS       s_is;
  IS       d_is;

  VecScatter  state_scatter;
  VecScatter  design_scatter;
  VecScatter *yi_scatter, *di_scatter;
  Vec         suby, subq, subd;
  Mat         Js, Jd, JsPrec, JsInv, JsBlock;

  PetscReal  alpha; /* Regularization parameter */
  PetscReal  beta;  /* Weight attributed to ||u||^2 in regularization functional */
  PetscReal  noise; /* Amount of noise to add to data */
  PetscReal *ones;
  Mat        Q;
  Mat        MQ;
  Mat        L;

  Mat Grad;
  Mat Av, Avwork;
  Mat Div, Divwork;
  Mat DSG;
  Mat Diag, Ones;

  Vec q;
  Vec ur; /* reference */

  Vec d;
  Vec dwork;

  Vec x; /* super vec of y,u */

  Vec y; /* state variables */
  Vec ywork;

  Vec ytrue;

  Vec u; /* design variables */
  Vec uwork;

  Vec utrue;

  Vec js_diag;

  Vec c; /* constraint vector */
  Vec cwork;

  Vec lwork;
  Vec S;
  Vec Swork, Twork, Sdiag, Ywork;
  Vec Av_u;

  KSP solver;
  PC  prec;

  PetscReal     tola, tolb, tolc, told;
  PetscInt      ksp_its;
  PetscInt      ksp_its_initial;
  PetscLogStage stages[10];
  PetscBool     use_ptap;
  PetscBool     use_lrc;
} AppCtx;

PetscErrorCode FormFunction(Tao, Vec, PetscReal *, void *);
PetscErrorCode FormGradient(Tao, Vec, Vec, void *);
PetscErrorCode FormFunctionGradient(Tao, Vec, PetscReal *, Vec, void *);
PetscErrorCode FormJacobianState(Tao, Vec, Mat, Mat, Mat, void *);
PetscErrorCode FormJacobianDesign(Tao, Vec, Mat, void *);
PetscErrorCode FormConstraints(Tao, Vec, Vec, void *);
PetscErrorCode FormHessian(Tao, Vec, Mat, Mat, void *);
PetscErrorCode Gather(Vec, Vec, VecScatter, Vec, VecScatter);
PetscErrorCode Scatter(Vec, Vec, VecScatter, Vec, VecScatter);
PetscErrorCode EllipticInitialize(AppCtx *);
PetscErrorCode EllipticDestroy(AppCtx *);
PetscErrorCode EllipticMonitor(Tao, void *);

PetscErrorCode StateBlockMatMult(Mat, Vec, Vec);
PetscErrorCode StateMatMult(Mat, Vec, Vec);

PetscErrorCode StateInvMatMult(Mat, Vec, Vec);
PetscErrorCode DesignMatMult(Mat, Vec, Vec);
PetscErrorCode DesignMatMultTranspose(Mat, Vec, Vec);

PetscErrorCode QMatMult(Mat, Vec, Vec);
PetscErrorCode QMatMultTranspose(Mat, Vec, Vec);

static char help[] = "";

int main(int argc, char **argv)
{
  Vec      x0;
  Tao      tao;
  AppCtx   user;
  PetscInt ntests = 1;
  PetscInt i;

  PetscFunctionBeginUser;
  PetscCall(PetscInitialize(&argc, &argv, (char *)0, help));
  user.mx = 8;
  PetscOptionsBegin(PETSC_COMM_WORLD, NULL, "elliptic example", NULL);
  PetscCall(PetscOptionsInt("-mx", "Number of grid points in each direction", "", user.mx, &user.mx, NULL));
  user.ns = 6;
  PetscCall(PetscOptionsInt("-ns", "Number of data samples (1<=ns<=8)", "", user.ns, &user.ns, NULL));
  user.ndata = 64;
  PetscCall(PetscOptionsInt("-ndata", "Numbers of data points per sample", "", user.ndata, &user.ndata, NULL));
  user.alpha = 0.1;
  PetscCall(PetscOptionsReal("-alpha", "Regularization parameter", "", user.alpha, &user.alpha, NULL));
  user.beta = 0.00001;
  PetscCall(PetscOptionsReal("-beta", "Weight attributed to ||u||^2 in regularization functional", "", user.beta, &user.beta, NULL));
  user.noise = 0.01;
  PetscCall(PetscOptionsReal("-noise", "Amount of noise to add to data", "", user.noise, &user.noise, NULL));

  user.use_ptap = PETSC_FALSE;
  PetscCall(PetscOptionsBool("-use_ptap", "Use ptap matrix for DSG", "", user.use_ptap, &user.use_ptap, NULL));
  user.use_lrc = PETSC_FALSE;
  PetscCall(PetscOptionsBool("-use_lrc", "Use lrc matrix for Js", "", user.use_lrc, &user.use_lrc, NULL));
  PetscCall(PetscOptionsInt("-ntests", "Number of times to repeat TaoSolve", "", ntests, &ntests, NULL));
  PetscOptionsEnd();

  user.m       = user.ns * user.mx * user.mx * user.mx; /* number of constraints */
  user.nstate  = user.m;
  user.ndesign = user.mx * user.mx * user.mx;
  user.n       = user.nstate + user.ndesign; /* number of variables */

  /* Create TAO solver and set desired solution method */
  PetscCall(TaoCreate(PETSC_COMM_WORLD, &tao));
  PetscCall(TaoSetType(tao, TAOLCL));

  /* Set up initial vectors and matrices */
  PetscCall(EllipticInitialize(&user));

  PetscCall(Gather(user.x, user.y, user.state_scatter, user.u, user.design_scatter));
  PetscCall(VecDuplicate(user.x, &x0));
  PetscCall(VecCopy(user.x, x0));

  /* Set solution vector with an initial guess */
  PetscCall(TaoSetSolution(tao, user.x));
  PetscCall(TaoSetObjective(tao, FormFunction, &user));
  PetscCall(TaoSetGradient(tao, NULL, FormGradient, &user));
  PetscCall(TaoSetConstraintsRoutine(tao, user.c, FormConstraints, &user));

  PetscCall(TaoSetJacobianStateRoutine(tao, user.Js, NULL, user.JsInv, FormJacobianState, &user));
  PetscCall(TaoSetJacobianDesignRoutine(tao, user.Jd, FormJacobianDesign, &user));

  PetscCall(TaoSetStateDesignIS(tao, user.s_is, user.d_is));
  PetscCall(TaoSetFromOptions(tao));

  /* SOLVE THE APPLICATION */
  PetscCall(PetscLogStageRegister("Trials", &user.stages[1]));
  PetscCall(PetscLogStagePush(user.stages[1]));
  for (i = 0; i < ntests; i++) {
    PetscCall(TaoSolve(tao));
    PetscCall(PetscPrintf(PETSC_COMM_WORLD, "KSP Iterations = %" PetscInt_FMT "\n", user.ksp_its));
    PetscCall(VecCopy(x0, user.x));
  }
  PetscCall(PetscLogStagePop());
  PetscCall(PetscBarrier((PetscObject)user.x));
  PetscCall(PetscPrintf(PETSC_COMM_WORLD, "KSP iterations within initialization: "));
  PetscCall(PetscPrintf(PETSC_COMM_WORLD, "%" PetscInt_FMT "\n", user.ksp_its_initial));

  PetscCall(TaoDestroy(&tao));
  PetscCall(VecDestroy(&x0));
  PetscCall(EllipticDestroy(&user));
  PetscCall(PetscFinalize());
  return 0;
}
/* ------------------------------------------------------------------- */
/*
   dwork = Qy - d
   lwork = L*(u-ur)
   f = 1/2 * (dwork.dwork + alpha*lwork.lwork)
*/
PetscErrorCode FormFunction(Tao tao, Vec X, PetscReal *f, void *ptr)
{
  PetscReal d1 = 0, d2 = 0;
  AppCtx   *user = (AppCtx *)ptr;

  PetscFunctionBegin;
  PetscCall(Scatter(X, user->y, user->state_scatter, user->u, user->design_scatter));
  PetscCall(MatMult(user->MQ, user->y, user->dwork));
  PetscCall(VecAXPY(user->dwork, -1.0, user->d));
  PetscCall(VecDot(user->dwork, user->dwork, &d1));
  PetscCall(VecWAXPY(user->uwork, -1.0, user->ur, user->u));
  PetscCall(MatMult(user->L, user->uwork, user->lwork));
  PetscCall(VecDot(user->lwork, user->lwork, &d2));
  *f = 0.5 * (d1 + user->alpha * d2);
  PetscFunctionReturn(PETSC_SUCCESS);
}

/* ------------------------------------------------------------------- */
/*
    state: g_s = Q' *(Qy - d)
    design: g_d = alpha*L'*L*(u-ur)
*/
PetscErrorCode FormGradient(Tao tao, Vec X, Vec G, void *ptr)
{
  AppCtx *user = (AppCtx *)ptr;

  PetscFunctionBegin;
  PetscCall(Scatter(X, user->y, user->state_scatter, user->u, user->design_scatter));
  PetscCall(MatMult(user->MQ, user->y, user->dwork));
  PetscCall(VecAXPY(user->dwork, -1.0, user->d));
  PetscCall(MatMultTranspose(user->MQ, user->dwork, user->ywork));
  PetscCall(VecWAXPY(user->uwork, -1.0, user->ur, user->u));
  PetscCall(MatMult(user->L, user->uwork, user->lwork));
  PetscCall(MatMultTranspose(user->L, user->lwork, user->uwork));
  PetscCall(VecScale(user->uwork, user->alpha));
  PetscCall(Gather(G, user->ywork, user->state_scatter, user->uwork, user->design_scatter));
  PetscFunctionReturn(PETSC_SUCCESS);
}

PetscErrorCode FormFunctionGradient(Tao tao, Vec X, PetscReal *f, Vec G, void *ptr)
{
  PetscReal d1, d2;
  AppCtx   *user = (AppCtx *)ptr;

  PetscFunctionBegin;
  PetscCall(Scatter(X, user->y, user->state_scatter, user->u, user->design_scatter));
  PetscCall(MatMult(user->MQ, user->y, user->dwork));
  PetscCall(VecAXPY(user->dwork, -1.0, user->d));
  PetscCall(VecDot(user->dwork, user->dwork, &d1));
  PetscCall(MatMultTranspose(user->MQ, user->dwork, user->ywork));

  PetscCall(VecWAXPY(user->uwork, -1.0, user->ur, user->u));
  PetscCall(MatMult(user->L, user->uwork, user->lwork));
  PetscCall(VecDot(user->lwork, user->lwork, &d2));
  PetscCall(MatMultTranspose(user->L, user->lwork, user->uwork));
  PetscCall(VecScale(user->uwork, user->alpha));
  *f = 0.5 * (d1 + user->alpha * d2);
  PetscCall(Gather(G, user->ywork, user->state_scatter, user->uwork, user->design_scatter));
  PetscFunctionReturn(PETSC_SUCCESS);
}

/* ------------------------------------------------------------------- */
/* A
MatShell object
*/
PetscErrorCode FormJacobianState(Tao tao, Vec X, Mat J, Mat JPre, Mat JInv, void *ptr)
{
  AppCtx *user = (AppCtx *)ptr;

  PetscFunctionBegin;
  PetscCall(Scatter(X, user->y, user->state_scatter, user->u, user->design_scatter));
  /* DSG = Div * (1/Av_u) * Grad */
  PetscCall(VecSet(user->uwork, 0));
  PetscCall(VecAXPY(user->uwork, -1.0, user->u));
  PetscCall(VecExp(user->uwork));
  PetscCall(MatMult(user->Av, user->uwork, user->Av_u));
  PetscCall(VecCopy(user->Av_u, user->Swork));
  PetscCall(VecReciprocal(user->Swork));
  if (user->use_ptap) {
    PetscCall(MatDiagonalSet(user->Diag, user->Swork, INSERT_VALUES));
    PetscCall(MatPtAP(user->Diag, user->Grad, MAT_REUSE_MATRIX, 1.0, &user->DSG));
  } else {
    PetscCall(MatCopy(user->Div, user->Divwork, SAME_NONZERO_PATTERN));
    PetscCall(MatDiagonalScale(user->Divwork, NULL, user->Swork));
    PetscCall(MatProductNumeric(user->DSG));
  }
  PetscFunctionReturn(PETSC_SUCCESS);
}
/* ------------------------------------------------------------------- */
/* B */
PetscErrorCode FormJacobianDesign(Tao tao, Vec X, Mat J, void *ptr)
{
  AppCtx *user = (AppCtx *)ptr;

  PetscFunctionBegin;
  PetscCall(Scatter(X, user->y, user->state_scatter, user->u, user->design_scatter));
  PetscFunctionReturn(PETSC_SUCCESS);
}

PetscErrorCode StateBlockMatMult(Mat J_shell, Vec X, Vec Y)
{
  PetscReal sum;
  AppCtx   *user;

  PetscFunctionBegin;
  PetscCall(MatShellGetContext(J_shell, &user));
  PetscCall(MatMult(user->DSG, X, Y));
  PetscCall(VecSum(X, &sum));
  sum /= user->ndesign;
  PetscCall(VecShift(Y, sum));
  PetscFunctionReturn(PETSC_SUCCESS);
}

PetscErrorCode StateMatMult(Mat J_shell, Vec X, Vec Y)
{
  PetscInt i;
  AppCtx  *user;

  PetscFunctionBegin;
  PetscCall(MatShellGetContext(J_shell, &user));
  if (user->ns == 1) {
    PetscCall(MatMult(user->JsBlock, X, Y));
  } else {
    for (i = 0; i < user->ns; i++) {
      PetscCall(Scatter(X, user->subq, user->yi_scatter[i], 0, 0));
      PetscCall(Scatter(Y, user->suby, user->yi_scatter[i], 0, 0));
      PetscCall(MatMult(user->JsBlock, user->subq, user->suby));
      PetscCall(Gather(Y, user->suby, user->yi_scatter[i], 0, 0));
    }
  }
  PetscFunctionReturn(PETSC_SUCCESS);
}

PetscErrorCode StateInvMatMult(Mat J_shell, Vec X, Vec Y)
{
  PetscInt its, i;
  AppCtx  *user;

  PetscFunctionBegin;
  PetscCall(MatShellGetContext(J_shell, &user));
  PetscCall(KSPSetOperators(user->solver, user->JsBlock, user->DSG));
  if (Y == user->ytrue) {
    /* First solve is done using true solution to set up problem */
    PetscCall(KSPSetTolerances(user->solver, 1e-8, PETSC_DEFAULT, PETSC_DEFAULT, PETSC_DEFAULT));
  } else {
    PetscCall(KSPSetTolerances(user->solver, PETSC_DEFAULT, PETSC_DEFAULT, PETSC_DEFAULT, PETSC_DEFAULT));
  }
  if (user->ns == 1) {
    PetscCall(KSPSolve(user->solver, X, Y));
    PetscCall(KSPGetIterationNumber(user->solver, &its));
    user->ksp_its += its;
  } else {
    for (i = 0; i < user->ns; i++) {
      PetscCall(Scatter(X, user->subq, user->yi_scatter[i], 0, 0));
      PetscCall(Scatter(Y, user->suby, user->yi_scatter[i], 0, 0));
      PetscCall(KSPSolve(user->solver, user->subq, user->suby));
      PetscCall(KSPGetIterationNumber(user->solver, &its));
      user->ksp_its += its;
      PetscCall(Gather(Y, user->suby, user->yi_scatter[i], 0, 0));
    }
  }
  PetscFunctionReturn(PETSC_SUCCESS);
}
PetscErrorCode QMatMult(Mat J_shell, Vec X, Vec Y)
{
  AppCtx  *user;
  PetscInt i;

  PetscFunctionBegin;
  PetscCall(MatShellGetContext(J_shell, &user));
  if (user->ns == 1) {
    PetscCall(MatMult(user->Q, X, Y));
  } else {
    for (i = 0; i < user->ns; i++) {
      PetscCall(Scatter(X, user->subq, user->yi_scatter[i], 0, 0));
      PetscCall(Scatter(Y, user->subd, user->di_scatter[i], 0, 0));
      PetscCall(MatMult(user->Q, user->subq, user->subd));
      PetscCall(Gather(Y, user->subd, user->di_scatter[i], 0, 0));
    }
  }
  PetscFunctionReturn(PETSC_SUCCESS);
}

PetscErrorCode QMatMultTranspose(Mat J_shell, Vec X, Vec Y)
{
  AppCtx  *user;
  PetscInt i;

  PetscFunctionBegin;
  PetscCall(MatShellGetContext(J_shell, &user));
  if (user->ns == 1) {
    PetscCall(MatMultTranspose(user->Q, X, Y));
  } else {
    for (i = 0; i < user->ns; i++) {
      PetscCall(Scatter(X, user->subd, user->di_scatter[i], 0, 0));
      PetscCall(Scatter(Y, user->suby, user->yi_scatter[i], 0, 0));
      PetscCall(MatMultTranspose(user->Q, user->subd, user->suby));
      PetscCall(Gather(Y, user->suby, user->yi_scatter[i], 0, 0));
    }
  }
  PetscFunctionReturn(PETSC_SUCCESS);
}

PetscErrorCode DesignMatMult(Mat J_shell, Vec X, Vec Y)
{
  PetscInt i;
  AppCtx  *user;

  PetscFunctionBegin;
  PetscCall(MatShellGetContext(J_shell, &user));

  /* sdiag(1./v) */
  PetscCall(VecSet(user->uwork, 0));
  PetscCall(VecAXPY(user->uwork, -1.0, user->u));
  PetscCall(VecExp(user->uwork));

  /* sdiag(1./((Av*(1./v)).^2)) */
  PetscCall(MatMult(user->Av, user->uwork, user->Swork));
  PetscCall(VecPointwiseMult(user->Swork, user->Swork, user->Swork));
  PetscCall(VecReciprocal(user->Swork));

  /* (Av * (sdiag(1./v) * b)) */
  PetscCall(VecPointwiseMult(user->uwork, user->uwork, X));
  PetscCall(MatMult(user->Av, user->uwork, user->Twork));

  /* (sdiag(1./((Av*(1./v)).^2)) * (Av * (sdiag(1./v) * b))) */
  PetscCall(VecPointwiseMult(user->Swork, user->Twork, user->Swork));

  if (user->ns == 1) {
    /* (sdiag(Grad*y(:,i)) */
    PetscCall(MatMult(user->Grad, user->y, user->Twork));

    /* Div * (sdiag(Grad*y(:,i)) * (sdiag(1./((Av*(1./v)).^2)) * (Av * (sdiag(1./v) * b)))) */
    PetscCall(VecPointwiseMult(user->Swork, user->Twork, user->Swork));
    PetscCall(MatMultTranspose(user->Grad, user->Swork, Y));
  } else {
    for (i = 0; i < user->ns; i++) {
      PetscCall(Scatter(user->y, user->suby, user->yi_scatter[i], 0, 0));
      PetscCall(Scatter(Y, user->subq, user->yi_scatter[i], 0, 0));

      PetscCall(MatMult(user->Grad, user->suby, user->Twork));
      PetscCall(VecPointwiseMult(user->Twork, user->Twork, user->Swork));
      PetscCall(MatMultTranspose(user->Grad, user->Twork, user->subq));
      PetscCall(Gather(user->y, user->suby, user->yi_scatter[i], 0, 0));
      PetscCall(Gather(Y, user->subq, user->yi_scatter[i], 0, 0));
    }
  }
  PetscFunctionReturn(PETSC_SUCCESS);
}

PetscErrorCode DesignMatMultTranspose(Mat J_shell, Vec X, Vec Y)
{
  PetscInt i;
  AppCtx  *user;

  PetscFunctionBegin;
  PetscCall(MatShellGetContext(J_shell, &user));
  PetscCall(VecZeroEntries(Y));

  /* Sdiag = 1./((Av*(1./v)).^2) */
  PetscCall(VecSet(user->uwork, 0));
  PetscCall(VecAXPY(user->uwork, -1.0, user->u));
  PetscCall(VecExp(user->uwork));
  PetscCall(MatMult(user->Av, user->uwork, user->Swork));
  PetscCall(VecPointwiseMult(user->Sdiag, user->Swork, user->Swork));
  PetscCall(VecReciprocal(user->Sdiag));

  for (i = 0; i < user->ns; i++) {
    PetscCall(Scatter(X, user->subq, user->yi_scatter[i], 0, 0));
    PetscCall(Scatter(user->y, user->suby, user->yi_scatter[i], 0, 0));

    /* Swork = (Div' * b(:,i)) */
    PetscCall(MatMult(user->Grad, user->subq, user->Swork));

    /* Twork = Grad*y(:,i) */
    PetscCall(MatMult(user->Grad, user->suby, user->Twork));

    /* Twork = sdiag(Twork) * Swork */
    PetscCall(VecPointwiseMult(user->Twork, user->Swork, user->Twork));

    /* Swork = pointwisemult(Sdiag,Twork) */
    PetscCall(VecPointwiseMult(user->Swork, user->Twork, user->Sdiag));

    /* Ywork = Av' * Swork */
    PetscCall(MatMultTranspose(user->Av, user->Swork, user->Ywork));

    /* Ywork = pointwisemult(uwork,Ywork) */
    PetscCall(VecPointwiseMult(user->Ywork, user->uwork, user->Ywork));
    PetscCall(VecAXPY(Y, 1.0, user->Ywork));
    PetscCall(Gather(user->y, user->suby, user->yi_scatter[i], 0, 0));
  }
  PetscFunctionReturn(PETSC_SUCCESS);
}

PetscErrorCode FormConstraints(Tao tao, Vec X, Vec C, void *ptr)
{
  /* C=Ay - q      A = Div * Sigma * Grad + hx*hx*hx*ones(n,n) */
  PetscReal sum;
  PetscInt  i;
  AppCtx   *user = (AppCtx *)ptr;

  PetscFunctionBegin;
  PetscCall(Scatter(X, user->y, user->state_scatter, user->u, user->design_scatter));
  if (user->ns == 1) {
    PetscCall(MatMult(user->Grad, user->y, user->Swork));
    PetscCall(VecPointwiseDivide(user->Swork, user->Swork, user->Av_u));
    PetscCall(MatMultTranspose(user->Grad, user->Swork, C));
    PetscCall(VecSum(user->y, &sum));
    sum /= user->ndesign;
    PetscCall(VecShift(C, sum));
  } else {
    for (i = 0; i < user->ns; i++) {
      PetscCall(Scatter(user->y, user->suby, user->yi_scatter[i], 0, 0));
      PetscCall(Scatter(C, user->subq, user->yi_scatter[i], 0, 0));
      PetscCall(MatMult(user->Grad, user->suby, user->Swork));
      PetscCall(VecPointwiseDivide(user->Swork, user->Swork, user->Av_u));
      PetscCall(MatMultTranspose(user->Grad, user->Swork, user->subq));

      PetscCall(VecSum(user->suby, &sum));
      sum /= user->ndesign;
      PetscCall(VecShift(user->subq, sum));

      PetscCall(Gather(user->y, user->suby, user->yi_scatter[i], 0, 0));
      PetscCall(Gather(C, user->subq, user->yi_scatter[i], 0, 0));
    }
  }
  PetscCall(VecAXPY(C, -1.0, user->q));
  PetscFunctionReturn(PETSC_SUCCESS);
}

PetscErrorCode Scatter(Vec x, Vec sub1, VecScatter scat1, Vec sub2, VecScatter scat2)
{
  PetscFunctionBegin;
  PetscCall(VecScatterBegin(scat1, x, sub1, INSERT_VALUES, SCATTER_FORWARD));
  PetscCall(VecScatterEnd(scat1, x, sub1, INSERT_VALUES, SCATTER_FORWARD));
  if (sub2) {
    PetscCall(VecScatterBegin(scat2, x, sub2, INSERT_VALUES, SCATTER_FORWARD));
    PetscCall(VecScatterEnd(scat2, x, sub2, INSERT_VALUES, SCATTER_FORWARD));
  }
  PetscFunctionReturn(PETSC_SUCCESS);
}

PetscErrorCode Gather(Vec x, Vec sub1, VecScatter scat1, Vec sub2, VecScatter scat2)
{
  PetscFunctionBegin;
  PetscCall(VecScatterBegin(scat1, sub1, x, INSERT_VALUES, SCATTER_REVERSE));
  PetscCall(VecScatterEnd(scat1, sub1, x, INSERT_VALUES, SCATTER_REVERSE));
  if (sub2) {
    PetscCall(VecScatterBegin(scat2, sub2, x, INSERT_VALUES, SCATTER_REVERSE));
    PetscCall(VecScatterEnd(scat2, sub2, x, INSERT_VALUES, SCATTER_REVERSE));
  }
  PetscFunctionReturn(PETSC_SUCCESS);
}

PetscErrorCode EllipticInitialize(AppCtx *user)
{
  PetscInt        m, n, i, j, k, l, linear_index, is, js, ks, ls, istart, iend, iblock;
  Vec             XX, YY, ZZ, XXwork, YYwork, ZZwork, UTwork;
  PetscReal      *x, *y, *z;
  PetscReal       h, meanut;
  PetscScalar     hinv, neg_hinv, half = 0.5, sqrt_beta;
  PetscInt        im, indx1, indx2, indy1, indy2, indz1, indz2, nx, ny, nz;
  IS              is_alldesign, is_allstate;
  IS              is_from_d;
  IS              is_from_y;
  PetscInt        lo, hi, hi2, lo2, ysubnlocal, dsubnlocal;
  const PetscInt *ranges, *subranges;
  PetscMPIInt     size;
  PetscReal       xri, yri, zri, xim, yim, zim, dx1, dx2, dy1, dy2, dz1, dz2, Dx, Dy, Dz;
  PetscScalar     v, vx, vy, vz;
  PetscInt        offset, subindex, subvec, nrank, kk;

  PetscScalar xr[64] = {0.4970, 0.8498, 0.7814, 0.6268, 0.7782, 0.6402, 0.3617, 0.3160, 0.3610, 0.5298, 0.6987, 0.3331, 0.7962, 0.5596, 0.3866, 0.6774, 0.5407, 0.4518, 0.6702, 0.6061, 0.7580, 0.8997,
                        0.5198, 0.8326, 0.2138, 0.9198, 0.3000, 0.2833, 0.8288, 0.7076, 0.1820, 0.0728, 0.8447, 0.2367, 0.3239, 0.6413, 0.3114, 0.4731, 0.1192, 0.9273, 0.5724, 0.4331, 0.5136, 0.3547,
                        0.4413, 0.2602, 0.5698, 0.7278, 0.5261, 0.6230, 0.2454, 0.3948, 0.7479, 0.6582, 0.4660, 0.5594, 0.7574, 0.1143, 0.5900, 0.1065, 0.4260, 0.3294, 0.8276, 0.0756};

  PetscScalar yr[64] = {0.7345, 0.9120, 0.9288, 0.7528, 0.4463, 0.4985, 0.2497, 0.6256, 0.3425, 0.9026, 0.6983, 0.4230, 0.7140, 0.2970, 0.4474, 0.8792, 0.6604, 0.2485, 0.7968, 0.6127, 0.1796, 0.2437,
                        0.5938, 0.6137, 0.3867, 0.5658, 0.4575, 0.1009, 0.0863, 0.3361, 0.0738, 0.3985, 0.6602, 0.1437, 0.0934, 0.5983, 0.5950, 0.0763, 0.0768, 0.2288, 0.5761, 0.1129, 0.3841, 0.6150,
                        0.6904, 0.6686, 0.1361, 0.4601, 0.4491, 0.3716, 0.1969, 0.6537, 0.6743, 0.6991, 0.4811, 0.5480, 0.1684, 0.4569, 0.6889, 0.8437, 0.3015, 0.2854, 0.8199, 0.2658};

  PetscScalar zr[64] = {0.7668, 0.8573, 0.2654, 0.2719, 0.1060, 0.1311, 0.6232, 0.2295, 0.8009, 0.2147, 0.2119, 0.9325, 0.4473, 0.3600, 0.3374, 0.3819, 0.4066, 0.5801, 0.1673, 0.0959, 0.4638, 0.8236,
                        0.8800, 0.2939, 0.2028, 0.8262, 0.2706, 0.6276, 0.9085, 0.6443, 0.8241, 0.0712, 0.1824, 0.7789, 0.4389, 0.8415, 0.7055, 0.6639, 0.3653, 0.2078, 0.1987, 0.2297, 0.4321, 0.8115,
                        0.4915, 0.7764, 0.4657, 0.4627, 0.4569, 0.4232, 0.8514, 0.0674, 0.3227, 0.1055, 0.6690, 0.6313, 0.9226, 0.5461, 0.4126, 0.2364, 0.6096, 0.7042, 0.3914, 0.0711};

  PetscFunctionBegin;
  PetscCallMPI(MPI_Comm_size(PETSC_COMM_WORLD, &size));
  PetscCall(PetscLogStageRegister("Elliptic Setup", &user->stages[0]));
  PetscCall(PetscLogStagePush(user->stages[0]));

  /* Create u,y,c,x */
  PetscCall(VecCreate(PETSC_COMM_WORLD, &user->u));
  PetscCall(VecCreate(PETSC_COMM_WORLD, &user->y));
  PetscCall(VecCreate(PETSC_COMM_WORLD, &user->c));
  PetscCall(VecSetSizes(user->u, PETSC_DECIDE, user->ndesign));
  PetscCall(VecSetFromOptions(user->u));
  PetscCall(VecGetLocalSize(user->u, &ysubnlocal));
  PetscCall(VecSetSizes(user->y, ysubnlocal * user->ns, user->nstate));
  PetscCall(VecSetSizes(user->c, ysubnlocal * user->ns, user->m));
  PetscCall(VecSetFromOptions(user->y));
  PetscCall(VecSetFromOptions(user->c));

  /*
     *******************************
     Create scatters for x <-> y,u
     *******************************

     If the state vector y and design vector u are partitioned as
     [y_1; y_2; ...; y_np] and [u_1; u_2; ...; u_np] (with np = # of processors),
     then the solution vector x is organized as
     [y_1; u_1; y_2; u_2; ...; y_np; u_np].
     The index sets user->s_is and user->d_is correspond to the indices of the
     state and design variables owned by the current processor.
  */
  PetscCall(VecCreate(PETSC_COMM_WORLD, &user->x));

  PetscCall(VecGetOwnershipRange(user->y, &lo, &hi));
  PetscCall(VecGetOwnershipRange(user->u, &lo2, &hi2));

  PetscCall(ISCreateStride(PETSC_COMM_SELF, hi - lo, lo, 1, &is_allstate));
  PetscCall(ISCreateStride(PETSC_COMM_SELF, hi - lo, lo + lo2, 1, &user->s_is));
  PetscCall(ISCreateStride(PETSC_COMM_SELF, hi2 - lo2, lo2, 1, &is_alldesign));
  PetscCall(ISCreateStride(PETSC_COMM_SELF, hi2 - lo2, hi + lo2, 1, &user->d_is));

  PetscCall(VecSetSizes(user->x, hi - lo + hi2 - lo2, user->n));
  PetscCall(VecSetFromOptions(user->x));

  PetscCall(VecScatterCreate(user->x, user->s_is, user->y, is_allstate, &user->state_scatter));
  PetscCall(VecScatterCreate(user->x, user->d_is, user->u, is_alldesign, &user->design_scatter));
  PetscCall(ISDestroy(&is_alldesign));
  PetscCall(ISDestroy(&is_allstate));
  /*
     *******************************
     Create scatter from y to y_1,y_2,...,y_ns
     *******************************
  */
  PetscCall(PetscMalloc1(user->ns, &user->yi_scatter));
  PetscCall(VecDuplicate(user->u, &user->suby));
  PetscCall(VecDuplicate(user->u, &user->subq));

  PetscCall(VecGetOwnershipRange(user->y, &lo2, &hi2));
  istart = 0;
  for (i = 0; i < user->ns; i++) {
    PetscCall(VecGetOwnershipRange(user->suby, &lo, &hi));
    PetscCall(ISCreateStride(PETSC_COMM_SELF, hi - lo, lo2 + istart, 1, &is_from_y));
    PetscCall(VecScatterCreate(user->y, is_from_y, user->suby, NULL, &user->yi_scatter[i]));
    istart = istart + hi - lo;
    PetscCall(ISDestroy(&is_from_y));
  }
  /*
     *******************************
     Create scatter from d to d_1,d_2,...,d_ns
     *******************************
  */
  PetscCall(VecCreate(PETSC_COMM_WORLD, &user->subd));
  PetscCall(VecSetSizes(user->subd, PETSC_DECIDE, user->ndata));
  PetscCall(VecSetFromOptions(user->subd));
  PetscCall(VecCreate(PETSC_COMM_WORLD, &user->d));
  PetscCall(VecGetLocalSize(user->subd, &dsubnlocal));
  PetscCall(VecSetSizes(user->d, dsubnlocal * user->ns, user->ndata * user->ns));
  PetscCall(VecSetFromOptions(user->d));
  PetscCall(PetscMalloc1(user->ns, &user->di_scatter));

  PetscCall(VecGetOwnershipRange(user->d, &lo2, &hi2));
  istart = 0;
  for (i = 0; i < user->ns; i++) {
    PetscCall(VecGetOwnershipRange(user->subd, &lo, &hi));
    PetscCall(ISCreateStride(PETSC_COMM_SELF, hi - lo, lo2 + istart, 1, &is_from_d));
    PetscCall(VecScatterCreate(user->d, is_from_d, user->subd, NULL, &user->di_scatter[i]));
    istart = istart + hi - lo;
    PetscCall(ISDestroy(&is_from_d));
  }

  PetscCall(PetscMalloc1(user->mx, &x));
  PetscCall(PetscMalloc1(user->mx, &y));
  PetscCall(PetscMalloc1(user->mx, &z));

  user->ksp_its         = 0;
  user->ksp_its_initial = 0;

  n         = user->mx * user->mx * user->mx;
  m         = 3 * user->mx * user->mx * (user->mx - 1);
  sqrt_beta = PetscSqrtScalar(user->beta);

  PetscCall(VecCreate(PETSC_COMM_WORLD, &XX));
  PetscCall(VecCreate(PETSC_COMM_WORLD, &user->q));
  PetscCall(VecSetSizes(XX, ysubnlocal, n));
  PetscCall(VecSetSizes(user->q, ysubnlocal * user->ns, user->m));
  PetscCall(VecSetFromOptions(XX));
  PetscCall(VecSetFromOptions(user->q));

  PetscCall(VecDuplicate(XX, &YY));
  PetscCall(VecDuplicate(XX, &ZZ));
  PetscCall(VecDuplicate(XX, &XXwork));
  PetscCall(VecDuplicate(XX, &YYwork));
  PetscCall(VecDuplicate(XX, &ZZwork));
  PetscCall(VecDuplicate(XX, &UTwork));
  PetscCall(VecDuplicate(XX, &user->utrue));

  /* map for striding q */
  PetscCall(VecGetOwnershipRanges(user->q, &ranges));
  PetscCall(VecGetOwnershipRanges(user->u, &subranges));

  PetscCall(VecGetOwnershipRange(user->q, &lo2, &hi2));
  PetscCall(VecGetOwnershipRange(user->u, &lo, &hi));
  /* Generate 3D grid, and collect ns (1<=ns<=8) right-hand-side vectors into user->q */
  h        = 1.0 / user->mx;
  hinv     = user->mx;
  neg_hinv = -hinv;

  PetscCall(VecGetOwnershipRange(XX, &istart, &iend));
  for (linear_index = istart; linear_index < iend; linear_index++) {
    i  = linear_index % user->mx;
    j  = ((linear_index - i) / user->mx) % user->mx;
    k  = ((linear_index - i) / user->mx - j) / user->mx;
    vx = h * (i + 0.5);
    vy = h * (j + 0.5);
    vz = h * (k + 0.5);
    PetscCall(VecSetValues(XX, 1, &linear_index, &vx, INSERT_VALUES));
    PetscCall(VecSetValues(YY, 1, &linear_index, &vy, INSERT_VALUES));
    PetscCall(VecSetValues(ZZ, 1, &linear_index, &vz, INSERT_VALUES));
    for (is = 0; is < 2; is++) {
      for (js = 0; js < 2; js++) {
        for (ks = 0; ks < 2; ks++) {
          ls = is * 4 + js * 2 + ks;
          if (ls < user->ns) {
            l = ls * n + linear_index;
            /* remap */
            subindex = l % n;
            subvec   = l / n;
            nrank    = 0;
            while (subindex >= subranges[nrank + 1]) nrank++;
            offset = subindex - subranges[nrank];
            istart = 0;
            for (kk = 0; kk < nrank; kk++) istart += user->ns * (subranges[kk + 1] - subranges[kk]);
            istart += (subranges[nrank + 1] - subranges[nrank]) * subvec;
            l = istart + offset;
            v = 100 * PetscSinScalar(2 * PETSC_PI * (vx + 0.25 * is)) * PetscSinScalar(2 * PETSC_PI * (vy + 0.25 * js)) * PetscSinScalar(2 * PETSC_PI * (vz + 0.25 * ks));
            PetscCall(VecSetValues(user->q, 1, &l, &v, INSERT_VALUES));
          }
        }
      }
    }
  }

  PetscCall(VecAssemblyBegin(XX));
  PetscCall(VecAssemblyEnd(XX));
  PetscCall(VecAssemblyBegin(YY));
  PetscCall(VecAssemblyEnd(YY));
  PetscCall(VecAssemblyBegin(ZZ));
  PetscCall(VecAssemblyEnd(ZZ));
  PetscCall(VecAssemblyBegin(user->q));
  PetscCall(VecAssemblyEnd(user->q));

  /* Compute true parameter function
     ut = exp(-((x-0.25)^2+(y-0.25)^2+(z-0.25)^2)/0.05) - exp((x-0.75)^2-(y-0.75)^2-(z-0.75))^2/0.05) */
  PetscCall(VecCopy(XX, XXwork));
  PetscCall(VecCopy(YY, YYwork));
  PetscCall(VecCopy(ZZ, ZZwork));

  PetscCall(VecShift(XXwork, -0.25));
  PetscCall(VecShift(YYwork, -0.25));
  PetscCall(VecShift(ZZwork, -0.25));

  PetscCall(VecPointwiseMult(XXwork, XXwork, XXwork));
  PetscCall(VecPointwiseMult(YYwork, YYwork, YYwork));
  PetscCall(VecPointwiseMult(ZZwork, ZZwork, ZZwork));

  PetscCall(VecCopy(XXwork, UTwork));
  PetscCall(VecAXPY(UTwork, 1.0, YYwork));
  PetscCall(VecAXPY(UTwork, 1.0, ZZwork));
  PetscCall(VecScale(UTwork, -20.0));
  PetscCall(VecExp(UTwork));
  PetscCall(VecCopy(UTwork, user->utrue));

  PetscCall(VecCopy(XX, XXwork));
  PetscCall(VecCopy(YY, YYwork));
  PetscCall(VecCopy(ZZ, ZZwork));

  PetscCall(VecShift(XXwork, -0.75));
  PetscCall(VecShift(YYwork, -0.75));
  PetscCall(VecShift(ZZwork, -0.75));

  PetscCall(VecPointwiseMult(XXwork, XXwork, XXwork));
  PetscCall(VecPointwiseMult(YYwork, YYwork, YYwork));
  PetscCall(VecPointwiseMult(ZZwork, ZZwork, ZZwork));

  PetscCall(VecCopy(XXwork, UTwork));
  PetscCall(VecAXPY(UTwork, 1.0, YYwork));
  PetscCall(VecAXPY(UTwork, 1.0, ZZwork));
  PetscCall(VecScale(UTwork, -20.0));
  PetscCall(VecExp(UTwork));

  PetscCall(VecAXPY(user->utrue, -1.0, UTwork));

  PetscCall(VecDestroy(&XX));
  PetscCall(VecDestroy(&YY));
  PetscCall(VecDestroy(&ZZ));
  PetscCall(VecDestroy(&XXwork));
  PetscCall(VecDestroy(&YYwork));
  PetscCall(VecDestroy(&ZZwork));
  PetscCall(VecDestroy(&UTwork));

  /* Initial guess and reference model */
  PetscCall(VecDuplicate(user->utrue, &user->ur));
  PetscCall(VecSum(user->utrue, &meanut));
  meanut = meanut / n;
  PetscCall(VecSet(user->ur, meanut));
  PetscCall(VecCopy(user->ur, user->u));

  /* Generate Grad matrix */
  PetscCall(MatCreate(PETSC_COMM_WORLD, &user->Grad));
  PetscCall(MatSetSizes(user->Grad, PETSC_DECIDE, ysubnlocal, m, n));
  PetscCall(MatSetFromOptions(user->Grad));
  PetscCall(MatMPIAIJSetPreallocation(user->Grad, 2, NULL, 2, NULL));
  PetscCall(MatSeqAIJSetPreallocation(user->Grad, 2, NULL));
  PetscCall(MatGetOwnershipRange(user->Grad, &istart, &iend));

  for (i = istart; i < iend; i++) {
    if (i < m / 3) {
      iblock = i / (user->mx - 1);
      j      = iblock * user->mx + (i % (user->mx - 1));
      PetscCall(MatSetValues(user->Grad, 1, &i, 1, &j, &neg_hinv, INSERT_VALUES));
      j = j + 1;
      PetscCall(MatSetValues(user->Grad, 1, &i, 1, &j, &hinv, INSERT_VALUES));
    }
    if (i >= m / 3 && i < 2 * m / 3) {
      iblock = (i - m / 3) / (user->mx * (user->mx - 1));
      j      = iblock * user->mx * user->mx + ((i - m / 3) % (user->mx * (user->mx - 1)));
      PetscCall(MatSetValues(user->Grad, 1, &i, 1, &j, &neg_hinv, INSERT_VALUES));
      j = j + user->mx;
      PetscCall(MatSetValues(user->Grad, 1, &i, 1, &j, &hinv, INSERT_VALUES));
    }
    if (i >= 2 * m / 3) {
      j = i - 2 * m / 3;
      PetscCall(MatSetValues(user->Grad, 1, &i, 1, &j, &neg_hinv, INSERT_VALUES));
      j = j + user->mx * user->mx;
      PetscCall(MatSetValues(user->Grad, 1, &i, 1, &j, &hinv, INSERT_VALUES));
    }
  }

  PetscCall(MatAssemblyBegin(user->Grad, MAT_FINAL_ASSEMBLY));
  PetscCall(MatAssemblyEnd(user->Grad, MAT_FINAL_ASSEMBLY));

  /* Generate arithmetic averaging matrix Av */
  PetscCall(MatCreate(PETSC_COMM_WORLD, &user->Av));
  PetscCall(MatSetSizes(user->Av, PETSC_DECIDE, ysubnlocal, m, n));
  PetscCall(MatSetFromOptions(user->Av));
  PetscCall(MatMPIAIJSetPreallocation(user->Av, 2, NULL, 2, NULL));
  PetscCall(MatSeqAIJSetPreallocation(user->Av, 2, NULL));
  PetscCall(MatGetOwnershipRange(user->Av, &istart, &iend));

  for (i = istart; i < iend; i++) {
    if (i < m / 3) {
      iblock = i / (user->mx - 1);
      j      = iblock * user->mx + (i % (user->mx - 1));
      PetscCall(MatSetValues(user->Av, 1, &i, 1, &j, &half, INSERT_VALUES));
      j = j + 1;
      PetscCall(MatSetValues(user->Av, 1, &i, 1, &j, &half, INSERT_VALUES));
    }
    if (i >= m / 3 && i < 2 * m / 3) {
      iblock = (i - m / 3) / (user->mx * (user->mx - 1));
      j      = iblock * user->mx * user->mx + ((i - m / 3) % (user->mx * (user->mx - 1)));
      PetscCall(MatSetValues(user->Av, 1, &i, 1, &j, &half, INSERT_VALUES));
      j = j + user->mx;
      PetscCall(MatSetValues(user->Av, 1, &i, 1, &j, &half, INSERT_VALUES));
    }
    if (i >= 2 * m / 3) {
      j = i - 2 * m / 3;
      PetscCall(MatSetValues(user->Av, 1, &i, 1, &j, &half, INSERT_VALUES));
      j = j + user->mx * user->mx;
      PetscCall(MatSetValues(user->Av, 1, &i, 1, &j, &half, INSERT_VALUES));
    }
  }

  PetscCall(MatAssemblyBegin(user->Av, MAT_FINAL_ASSEMBLY));
  PetscCall(MatAssemblyEnd(user->Av, MAT_FINAL_ASSEMBLY));

  PetscCall(MatCreate(PETSC_COMM_WORLD, &user->L));
  PetscCall(MatSetSizes(user->L, PETSC_DECIDE, ysubnlocal, m + n, n));
  PetscCall(MatSetFromOptions(user->L));
  PetscCall(MatMPIAIJSetPreallocation(user->L, 2, NULL, 2, NULL));
  PetscCall(MatSeqAIJSetPreallocation(user->L, 2, NULL));
  PetscCall(MatGetOwnershipRange(user->L, &istart, &iend));

  for (i = istart; i < iend; i++) {
    if (i < m / 3) {
      iblock = i / (user->mx - 1);
      j      = iblock * user->mx + (i % (user->mx - 1));
      PetscCall(MatSetValues(user->L, 1, &i, 1, &j, &neg_hinv, INSERT_VALUES));
      j = j + 1;
      PetscCall(MatSetValues(user->L, 1, &i, 1, &j, &hinv, INSERT_VALUES));
    }
    if (i >= m / 3 && i < 2 * m / 3) {
      iblock = (i - m / 3) / (user->mx * (user->mx - 1));
      j      = iblock * user->mx * user->mx + ((i - m / 3) % (user->mx * (user->mx - 1)));
      PetscCall(MatSetValues(user->L, 1, &i, 1, &j, &neg_hinv, INSERT_VALUES));
      j = j + user->mx;
      PetscCall(MatSetValues(user->L, 1, &i, 1, &j, &hinv, INSERT_VALUES));
    }
    if (i >= 2 * m / 3 && i < m) {
      j = i - 2 * m / 3;
      PetscCall(MatSetValues(user->L, 1, &i, 1, &j, &neg_hinv, INSERT_VALUES));
      j = j + user->mx * user->mx;
      PetscCall(MatSetValues(user->L, 1, &i, 1, &j, &hinv, INSERT_VALUES));
    }
    if (i >= m) {
      j = i - m;
      PetscCall(MatSetValues(user->L, 1, &i, 1, &j, &sqrt_beta, INSERT_VALUES));
    }
  }
  PetscCall(MatAssemblyBegin(user->L, MAT_FINAL_ASSEMBLY));
  PetscCall(MatAssemblyEnd(user->L, MAT_FINAL_ASSEMBLY));
  PetscCall(MatScale(user->L, PetscPowScalar(h, 1.5)));

  /* Generate Div matrix */
  if (!user->use_ptap) {
    /* Generate Div matrix */
    PetscCall(MatCreate(PETSC_COMM_WORLD, &user->Div));
    PetscCall(MatSetSizes(user->Div, ysubnlocal, PETSC_DECIDE, n, m));
    PetscCall(MatSetFromOptions(user->Div));
    PetscCall(MatMPIAIJSetPreallocation(user->Div, 4, NULL, 4, NULL));
    PetscCall(MatSeqAIJSetPreallocation(user->Div, 6, NULL));
    PetscCall(MatGetOwnershipRange(user->Grad, &istart, &iend));

    for (i = istart; i < iend; i++) {
      if (i < m / 3) {
        iblock = i / (user->mx - 1);
        j      = iblock * user->mx + (i % (user->mx - 1));
        PetscCall(MatSetValues(user->Div, 1, &j, 1, &i, &neg_hinv, INSERT_VALUES));
        j = j + 1;
        PetscCall(MatSetValues(user->Div, 1, &j, 1, &i, &hinv, INSERT_VALUES));
      }
      if (i >= m / 3 && i < 2 * m / 3) {
        iblock = (i - m / 3) / (user->mx * (user->mx - 1));
        j      = iblock * user->mx * user->mx + ((i - m / 3) % (user->mx * (user->mx - 1)));
        PetscCall(MatSetValues(user->Div, 1, &j, 1, &i, &neg_hinv, INSERT_VALUES));
        j = j + user->mx;
        PetscCall(MatSetValues(user->Div, 1, &j, 1, &i, &hinv, INSERT_VALUES));
      }
      if (i >= 2 * m / 3) {
        j = i - 2 * m / 3;
        PetscCall(MatSetValues(user->Div, 1, &j, 1, &i, &neg_hinv, INSERT_VALUES));
        j = j + user->mx * user->mx;
        PetscCall(MatSetValues(user->Div, 1, &j, 1, &i, &hinv, INSERT_VALUES));
      }
    }

    PetscCall(MatAssemblyBegin(user->Div, MAT_FINAL_ASSEMBLY));
    PetscCall(MatAssemblyEnd(user->Div, MAT_FINAL_ASSEMBLY));
    PetscCall(MatDuplicate(user->Div, MAT_SHARE_NONZERO_PATTERN, &user->Divwork));
  } else {
    PetscCall(MatCreate(PETSC_COMM_WORLD, &user->Diag));
    PetscCall(MatSetSizes(user->Diag, PETSC_DECIDE, PETSC_DECIDE, m, m));
    PetscCall(MatSetFromOptions(user->Diag));
    PetscCall(MatMPIAIJSetPreallocation(user->Diag, 1, NULL, 0, NULL));
    PetscCall(MatSeqAIJSetPreallocation(user->Diag, 1, NULL));
  }

  /* Build work vectors and matrices */
  PetscCall(VecCreate(PETSC_COMM_WORLD, &user->S));
  PetscCall(VecSetSizes(user->S, PETSC_DECIDE, m));
  PetscCall(VecSetFromOptions(user->S));

  PetscCall(VecCreate(PETSC_COMM_WORLD, &user->lwork));
  PetscCall(VecSetSizes(user->lwork, PETSC_DECIDE, m + user->mx * user->mx * user->mx));
  PetscCall(VecSetFromOptions(user->lwork));

  PetscCall(MatDuplicate(user->Av, MAT_SHARE_NONZERO_PATTERN, &user->Avwork));

  PetscCall(VecDuplicate(user->S, &user->Swork));
  PetscCall(VecDuplicate(user->S, &user->Sdiag));
  PetscCall(VecDuplicate(user->S, &user->Av_u));
  PetscCall(VecDuplicate(user->S, &user->Twork));
  PetscCall(VecDuplicate(user->y, &user->ywork));
  PetscCall(VecDuplicate(user->u, &user->Ywork));
  PetscCall(VecDuplicate(user->u, &user->uwork));
  PetscCall(VecDuplicate(user->u, &user->js_diag));
  PetscCall(VecDuplicate(user->c, &user->cwork));
  PetscCall(VecDuplicate(user->d, &user->dwork));

  /* Create a matrix-free shell user->Jd for computing B*x */
  PetscCall(MatCreateShell(PETSC_COMM_WORLD, ysubnlocal * user->ns, ysubnlocal, user->nstate, user->ndesign, user, &user->Jd));
  PetscCall(MatShellSetOperation(user->Jd, MATOP_MULT, (void (*)(void))DesignMatMult));
  PetscCall(MatShellSetOperation(user->Jd, MATOP_MULT_TRANSPOSE, (void (*)(void))DesignMatMultTranspose));

  /* Compute true state function ytrue given utrue */
  PetscCall(VecDuplicate(user->y, &user->ytrue));

  /* First compute Av_u = Av*exp(-u) */
  PetscCall(VecSet(user->uwork, 0));
  PetscCall(VecAXPY(user->uwork, -1.0, user->utrue)); /* Note: user->utrue */
  PetscCall(VecExp(user->uwork));
  PetscCall(MatMult(user->Av, user->uwork, user->Av_u));

  /* Next form DSG = Div*S*Grad */
  PetscCall(VecCopy(user->Av_u, user->Swork));
  PetscCall(VecReciprocal(user->Swork));
  if (user->use_ptap) {
    PetscCall(MatDiagonalSet(user->Diag, user->Swork, INSERT_VALUES));
    PetscCall(MatPtAP(user->Diag, user->Grad, MAT_INITIAL_MATRIX, 1.0, &user->DSG));
  } else {
    PetscCall(MatCopy(user->Div, user->Divwork, SAME_NONZERO_PATTERN));
    PetscCall(MatDiagonalScale(user->Divwork, NULL, user->Swork));

    PetscCall(MatMatMult(user->Divwork, user->Grad, MAT_INITIAL_MATRIX, 1.0, &user->DSG));
  }

  PetscCall(MatSetOption(user->DSG, MAT_SYMMETRIC, PETSC_TRUE));
  PetscCall(MatSetOption(user->DSG, MAT_SYMMETRY_ETERNAL, PETSC_TRUE));

  if (user->use_lrc == PETSC_TRUE) {
    v = PetscSqrtReal(1.0 / user->ndesign);
    PetscCall(PetscMalloc1(user->ndesign, &user->ones));

    for (i = 0; i < user->ndesign; i++) user->ones[i] = v;
    PetscCall(MatCreateDense(PETSC_COMM_WORLD, ysubnlocal, PETSC_DECIDE, user->ndesign, 1, user->ones, &user->Ones));
    PetscCall(MatCreateLRC(user->DSG, user->Ones, NULL, user->Ones, &user->JsBlock));
    PetscCall(MatSetUp(user->JsBlock));
  } else {
    /* Create matrix-free shell user->Js for computing (A + h^3*e*e^T)*x */
    PetscCall(MatCreateShell(PETSC_COMM_WORLD, ysubnlocal, ysubnlocal, user->ndesign, user->ndesign, user, &user->JsBlock));
    PetscCall(MatShellSetOperation(user->JsBlock, MATOP_MULT, (void (*)(void))StateBlockMatMult));
    PetscCall(MatShellSetOperation(user->JsBlock, MATOP_MULT_TRANSPOSE, (void (*)(void))StateBlockMatMult));
  }
  PetscCall(MatSetOption(user->JsBlock, MAT_SYMMETRIC, PETSC_TRUE));
  PetscCall(MatSetOption(user->JsBlock, MAT_SYMMETRY_ETERNAL, PETSC_TRUE));
  PetscCall(MatCreateShell(PETSC_COMM_WORLD, ysubnlocal * user->ns, ysubnlocal * user->ns, user->nstate, user->nstate, user, &user->Js));
  PetscCall(MatShellSetOperation(user->Js, MATOP_MULT, (void (*)(void))StateMatMult));
  PetscCall(MatShellSetOperation(user->Js, MATOP_MULT_TRANSPOSE, (void (*)(void))StateMatMult));
  PetscCall(MatSetOption(user->Js, MAT_SYMMETRIC, PETSC_TRUE));
  PetscCall(MatSetOption(user->Js, MAT_SYMMETRY_ETERNAL, PETSC_TRUE));

  PetscCall(MatCreateShell(PETSC_COMM_WORLD, ysubnlocal * user->ns, ysubnlocal * user->ns, user->nstate, user->nstate, user, &user->JsInv));
  PetscCall(MatShellSetOperation(user->JsInv, MATOP_MULT, (void (*)(void))StateInvMatMult));
  PetscCall(MatShellSetOperation(user->JsInv, MATOP_MULT_TRANSPOSE, (void (*)(void))StateInvMatMult));
  PetscCall(MatSetOption(user->JsInv, MAT_SYMMETRIC, PETSC_TRUE));
  PetscCall(MatSetOption(user->JsInv, MAT_SYMMETRY_ETERNAL, PETSC_TRUE));

  PetscCall(MatSetOption(user->DSG, MAT_SYMMETRIC, PETSC_TRUE));
  PetscCall(MatSetOption(user->DSG, MAT_SYMMETRY_ETERNAL, PETSC_TRUE));
  /* Now solve for ytrue */
  PetscCall(KSPCreate(PETSC_COMM_WORLD, &user->solver));
  PetscCall(KSPSetFromOptions(user->solver));

  PetscCall(KSPSetOperators(user->solver, user->JsBlock, user->DSG));

  PetscCall(MatMult(user->JsInv, user->q, user->ytrue));
  /* First compute Av_u = Av*exp(-u) */
  PetscCall(VecSet(user->uwork, 0));
  PetscCall(VecAXPY(user->uwork, -1.0, user->u)); /* Note: user->u */
  PetscCall(VecExp(user->uwork));
  PetscCall(MatMult(user->Av, user->uwork, user->Av_u));

  /* Next update DSG = Div*S*Grad  with user->u */
  PetscCall(VecCopy(user->Av_u, user->Swork));
  PetscCall(VecReciprocal(user->Swork));
  if (user->use_ptap) {
    PetscCall(MatDiagonalSet(user->Diag, user->Swork, INSERT_VALUES));
    PetscCall(MatPtAP(user->Diag, user->Grad, MAT_REUSE_MATRIX, 1.0, &user->DSG));
  } else {
    PetscCall(MatCopy(user->Div, user->Divwork, SAME_NONZERO_PATTERN));
    PetscCall(MatDiagonalScale(user->Divwork, NULL, user->Av_u));
    PetscCall(MatProductNumeric(user->DSG));
  }

  /* Now solve for y */

  PetscCall(MatMult(user->JsInv, user->q, user->y));

  user->ksp_its_initial = user->ksp_its;
  user->ksp_its         = 0;
  /* Construct projection matrix Q (blocks) */
  PetscCall(MatCreate(PETSC_COMM_WORLD, &user->Q));
  PetscCall(MatSetSizes(user->Q, dsubnlocal, ysubnlocal, user->ndata, user->ndesign));
  PetscCall(MatSetFromOptions(user->Q));
  PetscCall(MatMPIAIJSetPreallocation(user->Q, 8, NULL, 8, NULL));
  PetscCall(MatSeqAIJSetPreallocation(user->Q, 8, NULL));

  for (i = 0; i < user->mx; i++) {
    x[i] = h * (i + 0.5);
    y[i] = h * (i + 0.5);
    z[i] = h * (i + 0.5);
  }
  PetscCall(MatGetOwnershipRange(user->Q, &istart, &iend));

  nx = user->mx;
  ny = user->mx;
  nz = user->mx;
  for (i = istart; i < iend; i++) {
    xri = xr[i];
    im  = 0;
    xim = x[im];
    while (xri > xim && im < nx) {
      im  = im + 1;
      xim = x[im];
    }
    indx1 = im - 1;
    indx2 = im;
    dx1   = xri - x[indx1];
    dx2   = x[indx2] - xri;

    yri = yr[i];
    im  = 0;
    yim = y[im];
    while (yri > yim && im < ny) {
      im  = im + 1;
      yim = y[im];
    }
    indy1 = im - 1;
    indy2 = im;
    dy1   = yri - y[indy1];
    dy2   = y[indy2] - yri;

    zri = zr[i];
    im  = 0;
    zim = z[im];
    while (zri > zim && im < nz) {
      im  = im + 1;
      zim = z[im];
    }
    indz1 = im - 1;
    indz2 = im;
    dz1   = zri - z[indz1];
    dz2   = z[indz2] - zri;

    Dx = x[indx2] - x[indx1];
    Dy = y[indy2] - y[indy1];
    Dz = z[indz2] - z[indz1];

    j = indx1 + indy1 * nx + indz1 * nx * ny;
    v = (1 - dx1 / Dx) * (1 - dy1 / Dy) * (1 - dz1 / Dz);
    PetscCall(MatSetValues(user->Q, 1, &i, 1, &j, &v, INSERT_VALUES));

    j = indx1 + indy1 * nx + indz2 * nx * ny;
    v = (1 - dx1 / Dx) * (1 - dy1 / Dy) * (1 - dz2 / Dz);
    PetscCall(MatSetValues(user->Q, 1, &i, 1, &j, &v, INSERT_VALUES));

    j = indx1 + indy2 * nx + indz1 * nx * ny;
    v = (1 - dx1 / Dx) * (1 - dy2 / Dy) * (1 - dz1 / Dz);
    PetscCall(MatSetValues(user->Q, 1, &i, 1, &j, &v, INSERT_VALUES));

    j = indx1 + indy2 * nx + indz2 * nx * ny;
    v = (1 - dx1 / Dx) * (1 - dy2 / Dy) * (1 - dz2 / Dz);
    PetscCall(MatSetValues(user->Q, 1, &i, 1, &j, &v, INSERT_VALUES));

    j = indx2 + indy1 * nx + indz1 * nx * ny;
    v = (1 - dx2 / Dx) * (1 - dy1 / Dy) * (1 - dz1 / Dz);
    PetscCall(MatSetValues(user->Q, 1, &i, 1, &j, &v, INSERT_VALUES));

    j = indx2 + indy1 * nx + indz2 * nx * ny;
    v = (1 - dx2 / Dx) * (1 - dy1 / Dy) * (1 - dz2 / Dz);
    PetscCall(MatSetValues(user->Q, 1, &i, 1, &j, &v, INSERT_VALUES));

    j = indx2 + indy2 * nx + indz1 * nx * ny;
    v = (1 - dx2 / Dx) * (1 - dy2 / Dy) * (1 - dz1 / Dz);
    PetscCall(MatSetValues(user->Q, 1, &i, 1, &j, &v, INSERT_VALUES));

    j = indx2 + indy2 * nx + indz2 * nx * ny;
    v = (1 - dx2 / Dx) * (1 - dy2 / Dy) * (1 - dz2 / Dz);
    PetscCall(MatSetValues(user->Q, 1, &i, 1, &j, &v, INSERT_VALUES));
  }

  PetscCall(MatAssemblyBegin(user->Q, MAT_FINAL_ASSEMBLY));
  PetscCall(MatAssemblyEnd(user->Q, MAT_FINAL_ASSEMBLY));
  /* Create MQ (composed of blocks of Q */
  PetscCall(MatCreateShell(PETSC_COMM_WORLD, dsubnlocal * user->ns, PETSC_DECIDE, user->ndata * user->ns, user->nstate, user, &user->MQ));
  PetscCall(MatShellSetOperation(user->MQ, MATOP_MULT, (void (*)(void))QMatMult));
  PetscCall(MatShellSetOperation(user->MQ, MATOP_MULT_TRANSPOSE, (void (*)(void))QMatMultTranspose));

  /* Add noise to the measurement data */
  PetscCall(VecSet(user->ywork, 1.0));
  PetscCall(VecAYPX(user->ywork, user->noise, user->ytrue));
  PetscCall(MatMult(user->MQ, user->ywork, user->d));

  /* Now that initial conditions have been set, let the user pass tolerance options to the KSP solver */
  PetscCall(PetscFree(x));
  PetscCall(PetscFree(y));
  PetscCall(PetscFree(z));
  PetscCall(PetscLogStagePop());
  PetscFunctionReturn(PETSC_SUCCESS);
}

PetscErrorCode EllipticDestroy(AppCtx *user)
{
  PetscInt i;

  PetscFunctionBegin;
  PetscCall(MatDestroy(&user->DSG));
  PetscCall(KSPDestroy(&user->solver));
  PetscCall(MatDestroy(&user->Q));
  PetscCall(MatDestroy(&user->MQ));
  if (!user->use_ptap) {
    PetscCall(MatDestroy(&user->Div));
    PetscCall(MatDestroy(&user->Divwork));
  } else {
    PetscCall(MatDestroy(&user->Diag));
  }
  if (user->use_lrc) PetscCall(MatDestroy(&user->Ones));

  PetscCall(MatDestroy(&user->Grad));
  PetscCall(MatDestroy(&user->Av));
  PetscCall(MatDestroy(&user->Avwork));
  PetscCall(MatDestroy(&user->L));
  PetscCall(MatDestroy(&user->Js));
  PetscCall(MatDestroy(&user->Jd));
  PetscCall(MatDestroy(&user->JsBlock));
  PetscCall(MatDestroy(&user->JsInv));

  PetscCall(VecDestroy(&user->x));
  PetscCall(VecDestroy(&user->u));
  PetscCall(VecDestroy(&user->uwork));
  PetscCall(VecDestroy(&user->utrue));
  PetscCall(VecDestroy(&user->y));
  PetscCall(VecDestroy(&user->ywork));
  PetscCall(VecDestroy(&user->ytrue));
  PetscCall(VecDestroy(&user->c));
  PetscCall(VecDestroy(&user->cwork));
  PetscCall(VecDestroy(&user->ur));
  PetscCall(VecDestroy(&user->q));
  PetscCall(VecDestroy(&user->d));
  PetscCall(VecDestroy(&user->dwork));
  PetscCall(VecDestroy(&user->lwork));
  PetscCall(VecDestroy(&user->S));
  PetscCall(VecDestroy(&user->Swork));
  PetscCall(VecDestroy(&user->Sdiag));
  PetscCall(VecDestroy(&user->Ywork));
  PetscCall(VecDestroy(&user->Twork));
  PetscCall(VecDestroy(&user->Av_u));
  PetscCall(VecDestroy(&user->js_diag));
  PetscCall(ISDestroy(&user->s_is));
  PetscCall(ISDestroy(&user->d_is));
  PetscCall(VecDestroy(&user->suby));
  PetscCall(VecDestroy(&user->subd));
  PetscCall(VecDestroy(&user->subq));
  PetscCall(VecScatterDestroy(&user->state_scatter));
  PetscCall(VecScatterDestroy(&user->design_scatter));
  for (i = 0; i < user->ns; i++) {
    PetscCall(VecScatterDestroy(&user->yi_scatter[i]));
    PetscCall(VecScatterDestroy(&user->di_scatter[i]));
  }
  PetscCall(PetscFree(user->yi_scatter));
  PetscCall(PetscFree(user->di_scatter));
  if (user->use_lrc) {
    PetscCall(PetscFree(user->ones));
    PetscCall(MatDestroy(&user->Ones));
  }
  PetscFunctionReturn(PETSC_SUCCESS);
}

PetscErrorCode EllipticMonitor(Tao tao, void *ptr)
{
  Vec       X;
  PetscReal unorm, ynorm;
  AppCtx   *user = (AppCtx *)ptr;

  PetscFunctionBegin;
  PetscCall(TaoGetSolution(tao, &X));
  PetscCall(Scatter(X, user->ywork, user->state_scatter, user->uwork, user->design_scatter));
  PetscCall(VecAXPY(user->ywork, -1.0, user->ytrue));
  PetscCall(VecAXPY(user->uwork, -1.0, user->utrue));
  PetscCall(VecNorm(user->uwork, NORM_2, &unorm));
  PetscCall(VecNorm(user->ywork, NORM_2, &ynorm));
  PetscCall(PetscPrintf(MPI_COMM_WORLD, "||u-ut||=%g ||y-yt||=%g\n", (double)unorm, (double)ynorm));
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*TEST

   build:
      requires: !complex

   test:
      args: -tao_cmonitor -ns 1 -tao_type lcl -tao_gatol 1.e-3 -tao_max_it 11
      requires: !single

   test:
      suffix: 2
      args: -tao_cmonitor -tao_type lcl -tao_max_it 11 -use_ptap -use_lrc -ns 1 -tao_gatol 1.e-3
      requires: !single

TEST*/
