static char help[] = "This example demonstrates DMNetwork. It is used for testing parallel generation of dmnetwork, then redistribute. \n\\n";
/*
  Example: mpiexec -n <np> ./pipes -ts_max_steps 10
*/

#include "wash.h"

/*
  WashNetworkDistribute - proc[0] distributes sequential wash object
   Input Parameters:
.  comm - MPI communicator
.  wash - wash context with all network data in proc[0]

   Output Parameter:
.  wash - wash context with nedge, nvertex and edgelist distributed

   Note: The routine is used for testing parallel generation of dmnetwork, then redistribute.
*/
PetscErrorCode WashNetworkDistribute(MPI_Comm comm, Wash wash)
{
  PetscMPIInt rank, size, tag = 0;
  PetscInt    i, e, v, numEdges, numVertices, nedges, *eowners = NULL, estart, eend, *vtype = NULL, nvertices;
  PetscInt   *edgelist = wash->edgelist, *nvtx = NULL, *vtxDone = NULL;

  PetscFunctionBegin;
  PetscCallMPI(MPI_Comm_size(comm, &size));
  if (size == 1) PetscFunctionReturn(PETSC_SUCCESS);

  PetscCallMPI(MPI_Comm_rank(comm, &rank));
  numEdges    = wash->nedge;
  numVertices = wash->nvertex;

  /* (1) all processes get global and local number of edges */
  PetscCallMPI(MPI_Bcast(&numEdges, 1, MPIU_INT, 0, comm));
  nedges = numEdges / size; /* local nedges */
  if (rank == 0) nedges += numEdges - size * (numEdges / size);
  wash->Nedge = numEdges;
  wash->nedge = nedges;
  /* PetscCall(PetscPrintf(PETSC_COMM_SELF,"[%d] nedges %d, numEdges %d\n",rank,nedges,numEdges)); */

  PetscCall(PetscCalloc3(size + 1, &eowners, size, &nvtx, numVertices, &vtxDone));
  PetscCallMPI(MPI_Allgather(&nedges, 1, MPIU_INT, eowners + 1, 1, MPIU_INT, PETSC_COMM_WORLD));
  eowners[0] = 0;
  for (i = 2; i <= size; i++) eowners[i] += eowners[i - 1];

  estart = eowners[rank];
  eend   = eowners[rank + 1];
  /* PetscCall(PetscPrintf(PETSC_COMM_SELF,"[%d] own lists row %d - %d\n",rank,estart,eend)); */

  /* (2) distribute row block edgelist to all processors */
  if (rank == 0) {
    vtype = wash->vtype;
    for (i = 1; i < size; i++) {
      /* proc[0] sends edgelist to proc[i] */
      PetscCallMPI(MPI_Send(edgelist + 2 * eowners[i], 2 * (eowners[i + 1] - eowners[i]), MPIU_INT, i, tag, comm));

      /* proc[0] sends vtype to proc[i] */
      PetscCallMPI(MPI_Send(vtype + 2 * eowners[i], 2 * (eowners[i + 1] - eowners[i]), MPIU_INT, i, tag, comm));
    }
  } else {
    MPI_Status status;
    PetscCall(PetscMalloc1(2 * (eend - estart), &vtype));
    PetscCall(PetscMalloc1(2 * (eend - estart), &edgelist));

    PetscCallMPI(MPI_Recv(edgelist, 2 * (eend - estart), MPIU_INT, 0, tag, comm, &status));
    PetscCallMPI(MPI_Recv(vtype, 2 * (eend - estart), MPIU_INT, 0, tag, comm, &status));
  }

  wash->edgelist = edgelist;

  /* (3) all processes get global and local number of vertices, without ghost vertices */
  if (rank == 0) {
    for (i = 0; i < size; i++) {
      for (e = eowners[i]; e < eowners[i + 1]; e++) {
        v = edgelist[2 * e];
        if (!vtxDone[v]) {
          nvtx[i]++;
          vtxDone[v] = 1;
        }
        v = edgelist[2 * e + 1];
        if (!vtxDone[v]) {
          nvtx[i]++;
          vtxDone[v] = 1;
        }
      }
    }
  }
  PetscCallMPI(MPI_Bcast(&numVertices, 1, MPIU_INT, 0, PETSC_COMM_WORLD));
  PetscCallMPI(MPI_Scatter(nvtx, 1, MPIU_INT, &nvertices, 1, MPIU_INT, 0, PETSC_COMM_WORLD));
  PetscCall(PetscFree3(eowners, nvtx, vtxDone));

  wash->Nvertex = numVertices;
  wash->nvertex = nvertices;
  wash->vtype   = vtype;
  PetscFunctionReturn(PETSC_SUCCESS);
}

PetscErrorCode WASHIFunction(TS ts, PetscReal t, Vec X, Vec Xdot, Vec F, PetscCtx ctx)
{
  Wash               wash = (Wash)ctx;
  DM                 networkdm;
  Vec                localX, localXdot, localF, localXold;
  const PetscInt    *cone;
  PetscInt           vfrom, vto, offsetfrom, offsetto, varoffset;
  PetscInt           v, vStart, vEnd, e, eStart, eEnd;
  PetscInt           nend, type;
  PetscBool          ghost;
  PetscScalar       *farr, *juncf, *pipef;
  PetscReal          dt;
  Pipe               pipe;
  PipeField         *pipex, *pipexdot, *juncx;
  Junction           junction;
  DMDALocalInfo      info;
  const PetscScalar *xarr, *xdotarr, *xoldarr;

  PetscFunctionBegin;
  localX    = wash->localX;
  localXdot = wash->localXdot;

  PetscCall(TSGetSolution(ts, &localXold));
  PetscCall(TSGetDM(ts, &networkdm));
  PetscCall(TSGetTimeStep(ts, &dt));
  PetscCall(DMGetLocalVector(networkdm, &localF));

  /* Set F and localF as zero */
  PetscCall(VecSet(F, 0.0));
  PetscCall(VecSet(localF, 0.0));

  /* Update ghost values of locaX and locaXdot */
  PetscCall(DMGlobalToLocalBegin(networkdm, X, INSERT_VALUES, localX));
  PetscCall(DMGlobalToLocalEnd(networkdm, X, INSERT_VALUES, localX));

  PetscCall(DMGlobalToLocalBegin(networkdm, Xdot, INSERT_VALUES, localXdot));
  PetscCall(DMGlobalToLocalEnd(networkdm, Xdot, INSERT_VALUES, localXdot));

  PetscCall(VecGetArrayRead(localX, &xarr));
  PetscCall(VecGetArrayRead(localXdot, &xdotarr));
  PetscCall(VecGetArrayRead(localXold, &xoldarr));
  PetscCall(VecGetArray(localF, &farr));

  /* junction->type == JUNCTION:
           juncf[0] = -qJ + sum(qin); juncf[1] = qJ - sum(qout)
       junction->type == RESERVOIR (upper stream):
           juncf[0] = -hJ + H0; juncf[1] = qJ - sum(qout)
       junction->type == VALVE (down stream):
           juncf[0] =  -qJ + sum(qin); juncf[1] = qJ
  */
  /* Vertex/junction initialization */
  PetscCall(DMNetworkGetVertexRange(networkdm, &vStart, &vEnd));
  for (v = vStart; v < vEnd; v++) {
    PetscCall(DMNetworkIsGhostVertex(networkdm, v, &ghost));
    if (ghost) continue;

    PetscCall(DMNetworkGetComponent(networkdm, v, 0, &type, (void **)&junction, NULL));
    PetscCall(DMNetworkGetLocalVecOffset(networkdm, v, ALL_COMPONENTS, &varoffset));
    juncx = (PipeField *)(xarr + varoffset);
    juncf = (PetscScalar *)(farr + varoffset);

    juncf[0] = -juncx[0].q;
    juncf[1] = juncx[0].q;

    if (junction->type == RESERVOIR) { /* upstream reservoir */
      juncf[0] = juncx[0].h - wash->H0;
    }
  }

  /* Edge/pipe */
  PetscCall(DMNetworkGetEdgeRange(networkdm, &eStart, &eEnd));
  for (e = eStart; e < eEnd; e++) {
    PetscCall(DMNetworkGetComponent(networkdm, e, 0, &type, (void **)&pipe, NULL));
    PetscCall(DMNetworkGetLocalVecOffset(networkdm, e, ALL_COMPONENTS, &varoffset));
    pipex    = (PipeField *)(xarr + varoffset);
    pipexdot = (PipeField *)(xdotarr + varoffset);
    pipef    = (PetscScalar *)(farr + varoffset);

    /* Get some data into the pipe structure: note, some of these operations
     * might be redundant. Will it consume too much time? */
    pipe->dt   = dt;
    pipe->xold = (PipeField *)(xoldarr + varoffset);

    /* Evaluate F over this edge/pipe: pipef[1], ...,pipef[2*nend] */
    PetscCall(DMDAGetLocalInfo(pipe->da, &info));
    PetscCall(PipeIFunctionLocal_Lax(&info, t, pipex, pipexdot, pipef, pipe));

    /* Get boundary values from connected vertices */
    PetscCall(DMNetworkGetConnectedVertices(networkdm, e, &cone));
    vfrom = cone[0]; /* local ordering */
    vto   = cone[1];
    PetscCall(DMNetworkGetLocalVecOffset(networkdm, vfrom, ALL_COMPONENTS, &offsetfrom));
    PetscCall(DMNetworkGetLocalVecOffset(networkdm, vto, ALL_COMPONENTS, &offsetto));

    /* Evaluate upstream boundary */
    PetscCall(DMNetworkGetComponent(networkdm, vfrom, 0, &type, (void **)&junction, NULL));
    PetscCheck(junction->type == JUNCTION || junction->type == RESERVOIR, PETSC_COMM_SELF, PETSC_ERR_SUP, "junction type is not supported");
    juncx = (PipeField *)(xarr + offsetfrom);
    juncf = (PetscScalar *)(farr + offsetfrom);

    pipef[0] = pipex[0].h - juncx[0].h;
    juncf[1] -= pipex[0].q;

    /* Evaluate downstream boundary */
    PetscCall(DMNetworkGetComponent(networkdm, vto, 0, &type, (void **)&junction, NULL));
    PetscCheck(junction->type == JUNCTION || junction->type == VALVE, PETSC_COMM_SELF, PETSC_ERR_SUP, "junction type is not supported");
    juncx = (PipeField *)(xarr + offsetto);
    juncf = (PetscScalar *)(farr + offsetto);
    nend  = pipe->nnodes - 1;

    pipef[2 * nend + 1] = pipex[nend].h - juncx[0].h;
    juncf[0] += pipex[nend].q;
  }

  PetscCall(VecRestoreArrayRead(localX, &xarr));
  PetscCall(VecRestoreArrayRead(localXdot, &xdotarr));
  PetscCall(VecRestoreArray(localF, &farr));

  PetscCall(DMLocalToGlobalBegin(networkdm, localF, ADD_VALUES, F));
  PetscCall(DMLocalToGlobalEnd(networkdm, localF, ADD_VALUES, F));
  PetscCall(DMRestoreLocalVector(networkdm, &localF));
  /*
   PetscCall(PetscPrintf(PETSC_COMM_WORLD("F:\n"));
   PetscCall(VecView(F,PETSC_VIEWER_STDOUT_WORLD));
   */
  PetscFunctionReturn(PETSC_SUCCESS);
}

PetscErrorCode WASHSetInitialSolution(DM networkdm, Vec X, Wash wash)
{
  PetscInt           k, nx, vkey, vfrom, vto, offsetfrom, offsetto;
  PetscInt           type, varoffset;
  PetscInt           e, eStart, eEnd;
  Vec                localX;
  PetscScalar       *xarr;
  Pipe               pipe;
  Junction           junction;
  const PetscInt    *cone;
  const PetscScalar *xarray;

  PetscFunctionBegin;
  PetscCall(VecSet(X, 0.0));
  PetscCall(DMGetLocalVector(networkdm, &localX));
  PetscCall(VecGetArray(localX, &xarr));

  /* Edge */
  PetscCall(DMNetworkGetEdgeRange(networkdm, &eStart, &eEnd));
  for (e = eStart; e < eEnd; e++) {
    PetscCall(DMNetworkGetLocalVecOffset(networkdm, e, ALL_COMPONENTS, &varoffset));
    PetscCall(DMNetworkGetComponent(networkdm, e, 0, &type, (void **)&pipe, NULL));

    /* set initial values for this pipe */
    PetscCall(PipeComputeSteadyState(pipe, wash->Q0, wash->H0));
    PetscCall(VecGetSize(pipe->x, &nx));

    PetscCall(VecGetArrayRead(pipe->x, &xarray));
    /* copy pipe->x to xarray */
    for (k = 0; k < nx; k++) (xarr + varoffset)[k] = xarray[k];

    /* set boundary values into vfrom and vto */
    PetscCall(DMNetworkGetConnectedVertices(networkdm, e, &cone));
    vfrom = cone[0]; /* local ordering */
    vto   = cone[1];
    PetscCall(DMNetworkGetLocalVecOffset(networkdm, vfrom, ALL_COMPONENTS, &offsetfrom));
    PetscCall(DMNetworkGetLocalVecOffset(networkdm, vto, ALL_COMPONENTS, &offsetto));

    /* if vform is a head vertex: */
    PetscCall(DMNetworkGetComponent(networkdm, vfrom, 0, &vkey, (void **)&junction, NULL));
    if (junction->type == RESERVOIR) (xarr + offsetfrom)[1] = wash->H0; /* 1st H */

    /* if vto is an end vertex: */
    PetscCall(DMNetworkGetComponent(networkdm, vto, 0, &vkey, (void **)&junction, NULL));
    if (junction->type == VALVE) (xarr + offsetto)[0] = wash->QL; /* last Q */
    PetscCall(VecRestoreArrayRead(pipe->x, &xarray));
  }

  PetscCall(VecRestoreArray(localX, &xarr));
  PetscCall(DMLocalToGlobalBegin(networkdm, localX, ADD_VALUES, X));
  PetscCall(DMLocalToGlobalEnd(networkdm, localX, ADD_VALUES, X));
  PetscCall(DMRestoreLocalVector(networkdm, &localX));

#if 0
  PetscInt N;
  PetscCall(VecGetSize(X,&N));
  PetscCall(PetscPrintf(PETSC_COMM_WORLD,"initial solution %d:\n",N));
  PetscCall(VecView(X,PETSC_VIEWER_STDOUT_WORLD));
#endif
  PetscFunctionReturn(PETSC_SUCCESS);
}

PetscErrorCode TSDMNetworkMonitor(TS ts, PetscInt step, PetscReal t, Vec x, void *context)
{
  DMNetworkMonitor monitor;

  PetscFunctionBegin;
  monitor = (DMNetworkMonitor)context;
  PetscCall(DMNetworkMonitorView(monitor, x));
  PetscFunctionReturn(PETSC_SUCCESS);
}

PetscErrorCode PipesView(DM networkdm, PetscInt KeyPipe, Vec X)
{
  PetscInt   i, numkeys = 1, *blocksize, *numselectedvariable, **selectedvariables, n;
  IS         isfrom_q, isfrom_h, isfrom;
  Vec        Xto;
  VecScatter ctx;
  MPI_Comm   comm;

  PetscFunctionBegin;
  PetscCall(PetscObjectGetComm((PetscObject)networkdm, &comm));

  /* 1. Create isfrom_q for q-variable of pipes */
  PetscCall(PetscMalloc3(numkeys, &blocksize, numkeys, &numselectedvariable, numkeys, &selectedvariables));
  for (i = 0; i < numkeys; i++) {
    blocksize[i]           = 2;
    numselectedvariable[i] = 1;
    PetscCall(PetscMalloc1(numselectedvariable[i], &selectedvariables[i]));
    selectedvariables[i][0] = 0; /* q-variable */
  }
  PetscCall(DMNetworkCreateIS(networkdm, numkeys, &KeyPipe, blocksize, numselectedvariable, selectedvariables, &isfrom_q));

  /* 2. Create Xto and isto */
  PetscCall(ISGetLocalSize(isfrom_q, &n));
  PetscCall(VecCreate(comm, &Xto));
  PetscCall(VecSetSizes(Xto, n, PETSC_DECIDE));
  PetscCall(VecSetFromOptions(Xto));
  PetscCall(VecSet(Xto, 0.0));

  /* 3. Create scatter */
  PetscCall(VecScatterCreate(X, isfrom_q, Xto, NULL, &ctx));

  /* 4. Scatter to Xq */
  PetscCall(VecScatterBegin(ctx, X, Xto, INSERT_VALUES, SCATTER_FORWARD));
  PetscCall(VecScatterEnd(ctx, X, Xto, INSERT_VALUES, SCATTER_FORWARD));
  PetscCall(VecScatterDestroy(&ctx));
  PetscCall(ISDestroy(&isfrom_q));

  PetscCall(PetscPrintf(PETSC_COMM_WORLD, "Xq:\n"));
  PetscCall(VecView(Xto, PETSC_VIEWER_STDOUT_WORLD));

  /* 5. Create isfrom_h for h-variable of pipes; Create scatter; Scatter to Xh */
  for (i = 0; i < numkeys; i++) selectedvariables[i][0] = 1; /* h-variable */
  PetscCall(DMNetworkCreateIS(networkdm, numkeys, &KeyPipe, blocksize, numselectedvariable, selectedvariables, &isfrom_h));

  PetscCall(VecScatterCreate(X, isfrom_h, Xto, NULL, &ctx));
  PetscCall(VecScatterBegin(ctx, X, Xto, INSERT_VALUES, SCATTER_FORWARD));
  PetscCall(VecScatterEnd(ctx, X, Xto, INSERT_VALUES, SCATTER_FORWARD));
  PetscCall(VecScatterDestroy(&ctx));
  PetscCall(ISDestroy(&isfrom_h));

  PetscCall(PetscPrintf(PETSC_COMM_WORLD, "Xh:\n"));
  PetscCall(VecView(Xto, PETSC_VIEWER_STDOUT_WORLD));
  PetscCall(VecDestroy(&Xto));

  /* 6. Create isfrom for all pipe variables; Create scatter; Scatter to Xpipes */
  for (i = 0; i < numkeys; i++) blocksize[i] = -1; /* select all the variables of the i-th component */
  PetscCall(DMNetworkCreateIS(networkdm, numkeys, &KeyPipe, blocksize, NULL, NULL, &isfrom));
  PetscCall(ISDestroy(&isfrom));
  PetscCall(DMNetworkCreateIS(networkdm, numkeys, &KeyPipe, NULL, NULL, NULL, &isfrom));

  PetscCall(ISGetLocalSize(isfrom, &n));
  PetscCall(VecCreate(comm, &Xto));
  PetscCall(VecSetSizes(Xto, n, PETSC_DECIDE));
  PetscCall(VecSetFromOptions(Xto));
  PetscCall(VecSet(Xto, 0.0));

  PetscCall(VecScatterCreate(X, isfrom, Xto, NULL, &ctx));
  PetscCall(VecScatterBegin(ctx, X, Xto, INSERT_VALUES, SCATTER_FORWARD));
  PetscCall(VecScatterEnd(ctx, X, Xto, INSERT_VALUES, SCATTER_FORWARD));
  PetscCall(VecScatterDestroy(&ctx));
  PetscCall(ISDestroy(&isfrom));

  PetscCall(PetscPrintf(PETSC_COMM_WORLD, "Xpipes:\n"));
  PetscCall(VecView(Xto, PETSC_VIEWER_STDOUT_WORLD));

  /* 7. Free spaces */
  for (i = 0; i < numkeys; i++) PetscCall(PetscFree(selectedvariables[i]));
  PetscCall(PetscFree3(blocksize, numselectedvariable, selectedvariables));
  PetscCall(VecDestroy(&Xto));
  PetscFunctionReturn(PETSC_SUCCESS);
}

PetscErrorCode ISJunctionsView(DM networkdm, PetscInt KeyJunc)
{
  PetscInt    numkeys = 1;
  IS          isfrom;
  MPI_Comm    comm;
  PetscMPIInt rank;

  PetscFunctionBegin;
  PetscCall(PetscObjectGetComm((PetscObject)networkdm, &comm));
  PetscCallMPI(MPI_Comm_rank(comm, &rank));

  /* Create a global isfrom for all junction variables */
  PetscCall(DMNetworkCreateIS(networkdm, numkeys, &KeyJunc, NULL, NULL, NULL, &isfrom));
  PetscCall(PetscPrintf(PETSC_COMM_WORLD, "ISJunctions:\n"));
  PetscCall(ISView(isfrom, PETSC_VIEWER_STDOUT_WORLD));
  PetscCall(ISDestroy(&isfrom));

  /* Create a local isfrom for all junction variables */
  PetscCall(DMNetworkCreateLocalIS(networkdm, numkeys, &KeyJunc, NULL, NULL, NULL, &isfrom));
  if (rank == 0) {
    PetscCall(PetscPrintf(PETSC_COMM_SELF, "[%d] ISLocalJunctions:\n", rank));
    PetscCall(ISView(isfrom, PETSC_VIEWER_STDOUT_SELF));
  }
  PetscCall(ISDestroy(&isfrom));
  PetscFunctionReturn(PETSC_SUCCESS);
}

PetscErrorCode WashNetworkCleanUp(Wash wash)
{
  PetscMPIInt rank;

  PetscFunctionBegin;
  PetscCallMPI(MPI_Comm_rank(wash->comm, &rank));
  PetscCall(PetscFree(wash->edgelist));
  PetscCall(PetscFree(wash->vtype));
  if (rank == 0) PetscCall(PetscFree2(wash->junction, wash->pipe));
  PetscFunctionReturn(PETSC_SUCCESS);
}

PetscErrorCode WashNetworkCreate(MPI_Comm comm, PetscInt pipesCase, Wash *wash_ptr)
{
  PetscInt    npipes;
  PetscMPIInt rank;
  Wash        wash = NULL;
  PetscInt    i, numVertices, numEdges, *vtype;
  PetscInt   *edgelist;
  Junction    junctions = NULL;
  Pipe        pipes     = NULL;
  PetscBool   washdist  = PETSC_TRUE;

  PetscFunctionBegin;
  PetscCallMPI(MPI_Comm_rank(comm, &rank));

  PetscCall(PetscCalloc1(1, &wash));
  wash->comm       = comm;
  *wash_ptr        = wash;
  wash->Q0         = 0.477432; /* RESERVOIR */
  wash->H0         = 150.0;
  wash->HL         = 143.488; /* VALVE */
  wash->QL         = 0.0;
  wash->nnodes_loc = 0;

  numVertices = 0;
  numEdges    = 0;
  edgelist    = NULL;

  /* proc[0] creates a sequential wash and edgelist */
  PetscCall(PetscPrintf(PETSC_COMM_WORLD, "Setup pipesCase %" PetscInt_FMT "\n", pipesCase));

  /* Set global number of pipes, edges, and junctions */
  /*-------------------------------------------------*/
  switch (pipesCase) {
  case 0:
    /* pipeCase 0: */
    /* =================================================
    (RESERVOIR) v0 --E0--> v1--E1--> v2 --E2-->v3 (VALVE)
    ====================================================  */
    npipes = 3;
    PetscCall(PetscOptionsGetInt(NULL, NULL, "-npipes", &npipes, NULL));
    wash->nedge   = npipes;
    wash->nvertex = npipes + 1;

    /* Set local edges and vertices -- proc[0] sets entire network, then distributes */
    numVertices = 0;
    numEdges    = 0;
    edgelist    = NULL;
    if (rank == 0) {
      numVertices = wash->nvertex;
      numEdges    = wash->nedge;

      PetscCall(PetscCalloc1(2 * numEdges, &edgelist));
      for (i = 0; i < numEdges; i++) {
        edgelist[2 * i]     = i;
        edgelist[2 * i + 1] = i + 1;
      }

      /* Add network components */
      /*------------------------*/
      PetscCall(PetscCalloc2(numVertices, &junctions, numEdges, &pipes));

      /* vertex */
      for (i = 0; i < numVertices; i++) {
        junctions[i].id   = i;
        junctions[i].type = JUNCTION;
      }

      junctions[0].type               = RESERVOIR;
      junctions[numVertices - 1].type = VALVE;
    }
    break;
  case 1:
    /* pipeCase 1: */
    /* ==========================
                v2 (VALVE)
                ^
                |
               E2
                |
    v0 --E0--> v3--E1--> v1
  (RESERVOIR)            (RESERVOIR)
    =============================  */
    npipes        = 3;
    wash->nedge   = npipes;
    wash->nvertex = npipes + 1;

    /* Set local edges and vertices -- proc[0] sets entire network, then distributes */
    if (rank == 0) {
      numVertices = wash->nvertex;
      numEdges    = wash->nedge;

      PetscCall(PetscCalloc1(2 * numEdges, &edgelist));
      edgelist[0] = 0;
      edgelist[1] = 3; /* edge[0] */
      edgelist[2] = 3;
      edgelist[3] = 1; /* edge[1] */
      edgelist[4] = 3;
      edgelist[5] = 2; /* edge[2] */

      /* Add network components */
      /*------------------------*/
      PetscCall(PetscCalloc2(numVertices, &junctions, numEdges, &pipes));
      /* vertex */
      for (i = 0; i < numVertices; i++) {
        junctions[i].id   = i;
        junctions[i].type = JUNCTION;
      }

      junctions[0].type = RESERVOIR;
      junctions[1].type = VALVE;
      junctions[2].type = VALVE;
    }
    break;
  case 2:
    /* pipeCase 2: */
    /* ==========================
    (RESERVOIR)  v2--> E2
                       |
            v0 --E0--> v3--E1--> v1
    (RESERVOIR)               (VALVE)
    =============================  */

    /* Set application parameters -- to be used in function evaluations */
    npipes        = 3;
    wash->nedge   = npipes;
    wash->nvertex = npipes + 1;

    /* Set local edges and vertices -- proc[0] sets entire network, then distributes */
    if (rank == 0) {
      numVertices = wash->nvertex;
      numEdges    = wash->nedge;

      PetscCall(PetscCalloc1(2 * numEdges, &edgelist));
      edgelist[0] = 0;
      edgelist[1] = 3; /* edge[0] */
      edgelist[2] = 3;
      edgelist[3] = 1; /* edge[1] */
      edgelist[4] = 2;
      edgelist[5] = 3; /* edge[2] */

      /* Add network components */
      /*------------------------*/
      PetscCall(PetscCalloc2(numVertices, &junctions, numEdges, &pipes));
      /* vertex */
      for (i = 0; i < numVertices; i++) {
        junctions[i].id   = i;
        junctions[i].type = JUNCTION;
      }

      junctions[0].type = RESERVOIR;
      junctions[1].type = VALVE;
      junctions[2].type = RESERVOIR;
    }
    break;
  default:
    SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "not done yet");
  }

  /* set edge global id */
  for (i = 0; i < numEdges; i++) pipes[i].id = i;

  if (rank == 0) { /* set vtype for proc[0] */
    PetscInt v;
    PetscCall(PetscMalloc1(2 * numEdges, &vtype));
    for (i = 0; i < 2 * numEdges; i++) {
      v        = edgelist[i];
      vtype[i] = junctions[v].type;
    }
    wash->vtype = vtype;
  }

  *wash_ptr      = wash;
  wash->nedge    = numEdges;
  wash->nvertex  = numVertices;
  wash->edgelist = edgelist;
  wash->junction = junctions;
  wash->pipe     = pipes;

  /* Distribute edgelist to other processors */
  PetscCall(PetscOptionsGetBool(NULL, NULL, "-wash_distribute", &washdist, NULL));
  if (washdist) {
    /*
     PetscCall(PetscPrintf(PETSC_COMM_WORLD," Distribute sequential wash ...\n"));
     */
    PetscCall(WashNetworkDistribute(comm, wash));
  }
  PetscFunctionReturn(PETSC_SUCCESS);
}

/* ------------------------------------------------------- */
int main(int argc, char **argv)
{
  Wash              wash;
  Junction          junctions, junction;
  Pipe              pipe, pipes;
  PetscInt          KeyPipe, KeyJunction, *edgelist = NULL, *vtype = NULL;
  PetscInt          i, e, v, eStart, eEnd, vStart, vEnd, key, vkey, type;
  PetscInt          steps = 1, nedges, nnodes = 6;
  const PetscInt   *cone;
  DM                networkdm;
  PetscMPIInt       size, rank;
  PetscReal         ftime;
  Vec               X;
  TS                ts;
  TSConvergedReason reason;
  PetscBool         viewpipes, viewjuncs, monipipes = PETSC_FALSE, userJac = PETSC_TRUE, viewdm = PETSC_FALSE, viewX = PETSC_FALSE;
  PetscInt          pipesCase = 0;
  DMNetworkMonitor  monitor;
  MPI_Comm          comm;

  PetscFunctionBeginUser;
  PetscCall(PetscInitialize(&argc, &argv, "pOption", help));

  /* Read runtime options */
  PetscCall(PetscOptionsGetInt(NULL, NULL, "-case", &pipesCase, NULL));
  PetscCall(PetscOptionsGetBool(NULL, NULL, "-user_Jac", &userJac, NULL));
  PetscCall(PetscOptionsGetBool(NULL, NULL, "-pipe_monitor", &monipipes, NULL));
  PetscCall(PetscOptionsGetBool(NULL, NULL, "-viewdm", &viewdm, NULL));
  PetscCall(PetscOptionsGetBool(NULL, NULL, "-viewX", &viewX, NULL));
  PetscCall(PetscOptionsGetInt(NULL, NULL, "-npipenodes", &nnodes, NULL));

  /* Create networkdm */
  /*------------------*/
  PetscCall(DMNetworkCreate(PETSC_COMM_WORLD, &networkdm));
  PetscCall(PetscObjectGetComm((PetscObject)networkdm, &comm));
  PetscCallMPI(MPI_Comm_rank(comm, &rank));
  PetscCallMPI(MPI_Comm_size(comm, &size));

  if (size == 1 && monipipes) PetscCall(DMNetworkMonitorCreate(networkdm, &monitor));

  /* Register the components in the network */
  PetscCall(DMNetworkRegisterComponent(networkdm, "junctionstruct", sizeof(struct _p_Junction), &KeyJunction));
  PetscCall(DMNetworkRegisterComponent(networkdm, "pipestruct", sizeof(struct _p_Pipe), &KeyPipe));

  /* Create a distributed wash network (user-specific) */
  PetscCall(WashNetworkCreate(comm, pipesCase, &wash));
  nedges    = wash->nedge;
  edgelist  = wash->edgelist;
  vtype     = wash->vtype;
  junctions = wash->junction;
  pipes     = wash->pipe;

  /* Set up the network layout */
  PetscCall(DMNetworkSetNumSubNetworks(networkdm, PETSC_DECIDE, 1));
  PetscCall(DMNetworkAddSubnetwork(networkdm, NULL, nedges, edgelist, NULL));

  PetscCall(DMNetworkLayoutSetUp(networkdm));

  PetscCall(DMNetworkGetEdgeRange(networkdm, &eStart, &eEnd));
  PetscCall(DMNetworkGetVertexRange(networkdm, &vStart, &vEnd));
  /* PetscCall(PetscPrintf(PETSC_COMM_SELF,"[%d] eStart/End: %d - %d; vStart/End: %d - %d\n",rank,eStart,eEnd,vStart,vEnd)); */

  if (rank) { /* junctions[] and pipes[] for proc[0] are allocated in WashNetworkCreate() */
    /* vEnd - vStart = nvertices + number of ghost vertices! */
    PetscCall(PetscCalloc2(vEnd - vStart, &junctions, nedges, &pipes));
  }

  /* Add Pipe component and number of variables to all local edges */
  for (e = eStart; e < eEnd; e++) {
    pipes[e - eStart].nnodes = nnodes;
    PetscCall(DMNetworkAddComponent(networkdm, e, KeyPipe, &pipes[e - eStart], 2 * pipes[e - eStart].nnodes));

    if (size == 1 && monipipes) { /* Add monitor -- show Q_{pipes[e-eStart].id}? */
      pipes[e - eStart].length = 600.0;
      PetscCall(DMNetworkMonitorAdd(monitor, "Pipe Q", e, pipes[e - eStart].nnodes, 0, 2, 0.0, pipes[e - eStart].length, -0.8, 0.8, PETSC_TRUE));
      PetscCall(DMNetworkMonitorAdd(monitor, "Pipe H", e, pipes[e - eStart].nnodes, 1, 2, 0.0, pipes[e - eStart].length, -400.0, 800.0, PETSC_TRUE));
    }
  }

  /* Add Junction component and number of variables to all local vertices */
  for (v = vStart; v < vEnd; v++) PetscCall(DMNetworkAddComponent(networkdm, v, KeyJunction, &junctions[v - vStart], 2));

  if (size > 1) { /* must be called before DMSetUp()???. Other partitioners do not work yet??? -- cause crash in proc[0]! */
    DM               plexdm;
    PetscPartitioner part;
    PetscCall(DMNetworkGetPlex(networkdm, &plexdm));
    PetscCall(DMPlexGetPartitioner(plexdm, &part));
    PetscCall(PetscPartitionerSetType(part, PETSCPARTITIONERSIMPLE));
    PetscCall(PetscOptionsSetValue(NULL, "-dm_plex_csr_alg", "mat")); /* for parmetis */
  }

  /* Set up DM for use */
  PetscCall(DMSetUp(networkdm));
  if (viewdm) {
    PetscCall(PetscPrintf(PETSC_COMM_WORLD, "\nOriginal networkdm, DMView:\n"));
    PetscCall(DMView(networkdm, PETSC_VIEWER_STDOUT_WORLD));
  }

  /* Set user physical parameters to the components */
  for (e = eStart; e < eEnd; e++) {
    PetscCall(DMNetworkGetConnectedVertices(networkdm, e, &cone));
    /* vfrom */
    PetscCall(DMNetworkGetComponent(networkdm, cone[0], 0, &vkey, (void **)&junction, NULL));
    junction->type = (VertexType)vtype[2 * e];

    /* vto */
    PetscCall(DMNetworkGetComponent(networkdm, cone[1], 0, &vkey, (void **)&junction, NULL));
    junction->type = (VertexType)vtype[2 * e + 1];
  }

  PetscCall(WashNetworkCleanUp(wash));

  /* Network partitioning and distribution of data */
  PetscCall(DMNetworkDistribute(&networkdm, 0));
  if (viewdm) {
    PetscCall(PetscPrintf(PETSC_COMM_WORLD, "\nAfter DMNetworkDistribute, DMView:\n"));
    PetscCall(DMView(networkdm, PETSC_VIEWER_STDOUT_WORLD));
  }

  /* create vectors */
  PetscCall(DMCreateGlobalVector(networkdm, &X));
  PetscCall(DMCreateLocalVector(networkdm, &wash->localX));
  PetscCall(DMCreateLocalVector(networkdm, &wash->localXdot));

  /* PipeSetUp -- each process only sets its own pipes */
  /*---------------------------------------------------*/
  PetscCall(DMNetworkGetVertexRange(networkdm, &vStart, &vEnd));

  userJac = PETSC_TRUE;
  PetscCall(DMNetworkHasJacobian(networkdm, userJac, userJac));
  PetscCall(DMNetworkGetEdgeRange(networkdm, &eStart, &eEnd));
  for (e = eStart; e < eEnd; e++) { /* each edge has only one component, pipe */
    PetscCall(DMNetworkGetComponent(networkdm, e, 0, &type, (void **)&pipe, NULL));

    wash->nnodes_loc += pipe->nnodes;        /* local total number of nodes, will be used by PipesView() */
    PetscCall(PipeSetParameters(pipe, 600.0, /* length   */
                                0.5,         /* diameter */
                                1200.0,      /* a        */
                                0.018));     /* friction */
    PetscCall(PipeSetUp(pipe));

    if (userJac) {
      /* Create Jacobian matrix nonzero structures for a Pipe */
      Mat *J;
      PetscCall(PipeCreateJacobian(pipe, NULL, &J));
      PetscCall(DMNetworkEdgeSetMatrix(networkdm, e, J));
    }
  }

  if (userJac) {
    PetscCall(DMNetworkGetVertexRange(networkdm, &vStart, &vEnd));
    for (v = vStart; v < vEnd; v++) {
      Mat *J;
      PetscCall(JunctionCreateJacobian(networkdm, v, NULL, &J));
      PetscCall(DMNetworkVertexSetMatrix(networkdm, v, J));

      PetscCall(DMNetworkGetComponent(networkdm, v, 0, &vkey, (void **)&junction, NULL));
      junction->jacobian = J;
    }
  }

  /* Setup solver                                           */
  /*--------------------------------------------------------*/
  PetscCall(TSCreate(PETSC_COMM_WORLD, &ts));

  PetscCall(TSSetDM(ts, (DM)networkdm));
  PetscCall(TSSetIFunction(ts, NULL, WASHIFunction, wash));

  PetscCall(TSSetMaxSteps(ts, steps));
  PetscCall(TSSetExactFinalTime(ts, TS_EXACTFINALTIME_STEPOVER));
  PetscCall(TSSetTimeStep(ts, 0.1));
  PetscCall(TSSetType(ts, TSBEULER));
  if (size == 1 && monipipes) PetscCall(TSMonitorSet(ts, TSDMNetworkMonitor, monitor, NULL));
  PetscCall(TSSetFromOptions(ts));

  PetscCall(WASHSetInitialSolution(networkdm, X, wash));

  PetscCall(TSSolve(ts, X));

  PetscCall(TSGetSolveTime(ts, &ftime));
  PetscCall(TSGetStepNumber(ts, &steps));
  PetscCall(TSGetConvergedReason(ts, &reason));
  PetscCall(PetscPrintf(PETSC_COMM_WORLD, "%s at time %g after %" PetscInt_FMT " steps\n", TSConvergedReasons[reason], (double)ftime, steps));
  if (viewX) PetscCall(VecView(X, PETSC_VIEWER_STDOUT_WORLD));

  viewpipes = PETSC_FALSE;
  PetscCall(PetscOptionsGetBool(NULL, NULL, "-Jac_view", &viewpipes, NULL));
  if (viewpipes) {
    SNES snes;
    Mat  Jac;
    PetscCall(TSGetSNES(ts, &snes));
    PetscCall(SNESGetJacobian(snes, &Jac, NULL, NULL, NULL));
    PetscCall(MatView(Jac, PETSC_VIEWER_DRAW_WORLD));
  }

  /* View solutions */
  /* -------------- */
  viewpipes = PETSC_FALSE;
  PetscCall(PetscOptionsGetBool(NULL, NULL, "-pipe_view", &viewpipes, NULL));
  if (viewpipes) PetscCall(PipesView(networkdm, KeyPipe, X));

  /* Test IS */
  viewjuncs = PETSC_FALSE;
  PetscCall(PetscOptionsGetBool(NULL, NULL, "-isjunc_view", &viewjuncs, NULL));
  if (viewjuncs) PetscCall(ISJunctionsView(networkdm, KeyJunction));

  /* Free spaces */
  /* ----------- */
  PetscCall(TSDestroy(&ts));
  PetscCall(VecDestroy(&X));
  PetscCall(VecDestroy(&wash->localX));
  PetscCall(VecDestroy(&wash->localXdot));

  /* Destroy objects from each pipe that are created in PipeSetUp() */
  PetscCall(DMNetworkGetEdgeRange(networkdm, &eStart, &eEnd));
  for (i = eStart; i < eEnd; i++) {
    PetscCall(DMNetworkGetComponent(networkdm, i, 0, &key, (void **)&pipe, NULL));
    PetscCall(PipeDestroy(&pipe));
  }
  if (userJac) {
    for (v = vStart; v < vEnd; v++) {
      PetscCall(DMNetworkGetComponent(networkdm, v, 0, &vkey, (void **)&junction, NULL));
      PetscCall(JunctionDestroyJacobian(networkdm, v, junction));
    }
  }

  if (size == 1 && monipipes) PetscCall(DMNetworkMonitorDestroy(&monitor));
  PetscCall(DMDestroy(&networkdm));
  PetscCall(PetscFree(wash));

  if (rank) PetscCall(PetscFree2(junctions, pipes));
  PetscCall(PetscFinalize());
  return 0;
}

/*TEST

   build:
     depends: pipeInterface.c pipeImpls.c
     #TODO: bugs in DMNetwork causing segfault with __float128
     requires: mumps !__float128

   test:
      args: -ts_monitor -case 1 -ts_max_steps 1 -pc_factor_mat_solver_type mumps -options_left no -viewX
      localrunfiles: pOption
      output_file: output/pipes_1.out

   test:
      suffix: 2
      nsize: 2
      args: -ts_monitor -case 1 -ts_max_steps 1 -pc_factor_mat_solver_type mumps -petscpartitioner_type simple -options_left no -viewX
      localrunfiles: pOption
      output_file: output/pipes_2.out

   test:
      suffix: 3
      nsize: 2
      args: -ts_monitor -case 0 -ts_max_steps 1 -pc_factor_mat_solver_type mumps -petscpartitioner_type simple -options_left no -viewX
      localrunfiles: pOption
      output_file: output/pipes_3.out

   test:
      suffix: 4
      args: -ts_monitor -case 2 -ts_max_steps 1 -pc_factor_mat_solver_type mumps -options_left no -viewX
      localrunfiles: pOption
      output_file: output/pipes_4.out

   test:
      suffix: 5
      nsize: 3
      args: -ts_monitor -case 2 -ts_max_steps 10 -pc_factor_mat_solver_type mumps -petscpartitioner_type simple -options_left no -viewX
      localrunfiles: pOption
      output_file: output/pipes_5.out

   test:
      suffix: 6
      nsize: 2
      args: -ts_monitor -case 1 -ts_max_steps 1 -pc_factor_mat_solver_type mumps -petscpartitioner_type simple -options_left no -wash_distribute 0 -viewX
      localrunfiles: pOption
      output_file: output/pipes_6.out

   test:
      suffix: 7
      nsize: 2
      args: -ts_monitor -case 2 -ts_max_steps 1 -pc_factor_mat_solver_type mumps -petscpartitioner_type simple -options_left no -wash_distribute 0 -viewX
      localrunfiles: pOption
      output_file: output/pipes_7.out

   test:
      suffix: 8
      nsize: 2
      requires: parmetis
      args: -ts_monitor -case 2 -ts_max_steps 1 -pc_factor_mat_solver_type mumps -petscpartitioner_type parmetis -options_left no -wash_distribute 1
      localrunfiles: pOption
      output_file: output/pipes_8.out

   test:
      suffix: 9
      nsize: 2
      args: -case 0 -ts_max_steps 1 -pc_factor_mat_solver_type mumps -petscpartitioner_type simple -options_left no -wash_distribute 0 -pipe_view
      localrunfiles: pOption
      output_file: output/pipes_9.out

   test:
      suffix: 10
      nsize: 2
      args: -case 0 -ts_max_steps 1 -pc_factor_mat_solver_type mumps -petscpartitioner_type simple -options_left no -wash_distribute 0 -isjunc_view
      localrunfiles: pOption
      output_file: output/pipes_10.out

TEST*/
