#ifdef PETSC_RCS_HEADER
static char vcid[] = "$Id: aomapping.c,v 1.3 2000/01/10 03:15:40 knepley Exp $";
#endif

/*
  These AO application ordering routines do not require that the input
  be a permutation, but merely a 1-1 mapping. This implementation still
  keeps the entire ordering on each processor.
*/

#include "src/dm/ao/aoimpl.h"
#include "petscsys.h"

typedef struct {
  int N;
  int *app;       /* app[i] is the partner for petsc[appPerm[i]] */
  int *appPerm;
  int *petsc;     /* petsc[j] is the partner for app[petscPerm[j]] */
  int *petscPerm;
} AO_Mapping;

#undef __FUNCT__  
#define __FUNCT__ "AODestroy_Mapping"
int AODestroy_Mapping(AO ao)
{
  AO_Mapping *aomap = (AO_Mapping *) ao->data;
  int         ierr;

  PetscFunctionBegin;
  ierr = PetscFree(aomap->app);                                                                           CHKERRQ(ierr);
  ierr = PetscFree(ao->data);                                                                             CHKERRQ(ierr);
  PetscFunctionReturn(0);
}

#undef __FUNCT__  
#define __FUNCT__ "AOView_Mapping"
int AOView_Mapping(AO ao, PetscViewer viewer)
{
  AO_Mapping *aomap = (AO_Mapping *) ao->data;
  int         rank, i;
  PetscTruth  isascii;
  int         ierr;

  PetscFunctionBegin;
  ierr = MPI_Comm_rank(ao->comm, &rank);                                                                  CHKERRQ(ierr);
  if (rank) PetscFunctionReturn(0);

  if (!viewer) {
    viewer = PETSC_VIEWER_STDOUT_SELF; 
  }

  ierr = PetscTypeCompare((PetscObject) viewer, PETSC_VIEWER_ASCII, &isascii);                            CHKERRQ(ierr);
  if (isascii == PETSC_TRUE) {
    PetscViewerASCIIPrintf(viewer, "Number of elements in ordering %d\n", aomap->N);
    PetscViewerASCIIPrintf(viewer, "   App.   PETSc\n");
    for(i = 0; i < aomap->N; i++) {
      PetscViewerASCIIPrintf(viewer, "%d   %d    %d\n", i, aomap->app[i], aomap->petsc[aomap->appPerm[i]]);
    }
  }
  PetscFunctionReturn(0);
}

#undef __FUNCT__  
#define __FUNCT__ "AOPetscToApplication_Mapping"
int AOPetscToApplication_Mapping(AO ao, int n, int *ia)
{
  AO_Mapping *aomap = (AO_Mapping *) ao->data;
  int        *app   = aomap->app;
  int        *petsc = aomap->petsc;
  int        *perm  = aomap->petscPerm;
  int         N     = aomap->N;
  int         low, high, mid=0;
  int         idex;
  int         i;

  /* It would be possible to use a single bisection search, which
     recursively divided the indices to be converted, and searched
     partitions which contained an index. This would result in
     better running times if indices are clustered.
  */
  PetscFunctionBegin;
  for(i = 0; i < n; i++) {
    idex = ia[i];
    if (idex < 0) continue;
    /* Use bisection since the array is sorted */
    low  = 0;
    high = N - 1;
    while (low <= high) {
      mid = (low + high)/2;
      if (idex == petsc[mid]) {
        break;
      } else if (idex < petsc[mid]) {
        high = mid - 1;
      } else {
        low  = mid + 1;
      }
    }
    if (low > high) SETERRQ1(PETSC_ERR_ARG_OUTOFRANGE, "Invalid input index %d", idex);
    ia[i] = app[perm[mid]];
  }
  PetscFunctionReturn(0);
}

#undef __FUNCT__  
#define __FUNCT__ "AOApplicationToPetsc_Mapping"
int AOApplicationToPetsc_Mapping(AO ao, int n, int *ia)
{
  AO_Mapping *aomap = (AO_Mapping *) ao->data;
  int        *app   = aomap->app;
  int        *petsc = aomap->petsc;
  int        *perm  = aomap->appPerm;
  int         N     = aomap->N;
  int         low, high, mid=0;
  int         idex;
  int         i;

  /* It would be possible to use a single bisection search, which
     recursively divided the indices to be converted, and searched
     partitions which contained an index. This would result in
     better running times if indices are clustered.
  */
  PetscFunctionBegin;
  for(i = 0; i < n; i++) {
    idex = ia[i];
    if (idex < 0) continue;
    /* Use bisection since the array is sorted */
    low  = 0;
    high = N - 1;
    while (low <= high) {
      mid = (low + high)/2;
      if (idex == app[mid]) {
        break;
      } else if (idex < app[mid]) {
        high = mid - 1;
      } else {
        low  = mid + 1;
      }
    }
    if (low > high) SETERRQ1(PETSC_ERR_ARG_OUTOFRANGE, "Invalid input index %d", idex);
    ia[i] = petsc[perm[mid]];
  }
  PetscFunctionReturn(0);
}

static struct _AOOps AOps = {AOView_Mapping,
                             AODestroy_Mapping,
                             AOPetscToApplication_Mapping,
                             AOApplicationToPetsc_Mapping,
                             PETSC_NULL,
                             PETSC_NULL,
                             PETSC_NULL,
                             PETSC_NULL};

EXTERN_C_BEGIN
#undef __FUNCT__  
#define __FUNCT__ "AOSerialize_Mapping" 
int AOSerialize_Mapping(MPI_Comm comm, AO *ao, PetscViewer viewer, PetscTruth store)
{
  AO          a;
  AO_Mapping *m;
  int         fd;
  int         ierr;

  PetscFunctionBegin;
  ierr = PetscViewerBinaryGetDescriptor(viewer, &fd);                                                     CHKERRQ(ierr);
  if (store) {
    a = *ao;
    m = (AO_Mapping *) a->data;
    ierr = PetscBinaryWrite(fd, &a->N,         1,    PETSC_INT, 0);                                       CHKERRQ(ierr);
    ierr = PetscBinaryWrite(fd, &a->n,         1,    PETSC_INT, 0);                                       CHKERRQ(ierr);
    ierr = PetscBinaryWrite(fd, &m->N,         1,    PETSC_INT, 0);                                       CHKERRQ(ierr);
    ierr = PetscBinaryWrite(fd,  m->app,       m->N, PETSC_INT, 0);                                       CHKERRQ(ierr);
    ierr = PetscBinaryWrite(fd,  m->appPerm,   m->N, PETSC_INT, 0);                                       CHKERRQ(ierr);
    ierr = PetscBinaryWrite(fd,  m->petsc,     m->N, PETSC_INT, 0);                                       CHKERRQ(ierr);
    ierr = PetscBinaryWrite(fd,  m->petscPerm, m->N, PETSC_INT, 0);                                       CHKERRQ(ierr);
  } else {
    PetscHeaderCreate(a, _p_AO, struct _AOOps, AO_COOKIE, AO_MAPPING, "AO", comm, AODestroy, AOView); 
    PetscLogObjectCreate(a);
    PetscLogObjectMemory(a, sizeof(AO_Mapping) + sizeof(struct _p_AO));
    ierr = PetscNew(AO_Mapping, &m);                                                                      CHKERRQ(ierr);
    a->data = (void *) m;
    ierr = PetscMemcpy(a->ops, &AOps, sizeof(AOps));                                                      CHKERRQ(ierr);

    ierr = PetscBinaryRead(fd, &a->N,     1,    PETSC_INT);                                               CHKERRQ(ierr);
    ierr = PetscBinaryRead(fd, &a->n,     1,    PETSC_INT);                                               CHKERRQ(ierr);
    ierr = PetscBinaryRead(fd, &m->N,     1,    PETSC_INT);                                               CHKERRQ(ierr);
    ierr = PetscMalloc(m->N*4 * sizeof(int), &m->app);                                                    CHKERRQ(ierr);
    m->appPerm   = m->app     + m->N;
    m->petsc     = m->appPerm + m->N;
    m->petscPerm = m->petsc   + m->N;
    PetscLogObjectMemory(a, m->N*4 * sizeof(int));
    ierr = PetscBinaryRead(fd,  m->app,       m->N, PETSC_INT);                                           CHKERRQ(ierr);
    ierr = PetscBinaryRead(fd,  m->appPerm,   m->N, PETSC_INT);                                           CHKERRQ(ierr);
    ierr = PetscBinaryRead(fd,  m->petsc,     m->N, PETSC_INT);                                           CHKERRQ(ierr);
    ierr = PetscBinaryRead(fd,  m->petscPerm, m->N, PETSC_INT);                                           CHKERRQ(ierr);
    *ao = a;
  }

  PetscFunctionReturn(0);
}
EXTERN_C_END

#undef __FUNCT__  
#define __FUNCT__ "AOMappingHasApplicationIndex"
/*@C
  AOMappingHasApplicationIndex - Searches for the supplied application index.

  Input Parameters:
+ ao       - The AOMapping
- index    - The application index

  Output Parameter:
. hasIndex - Flag is PETSC_TRUE if the index exists

  Level: intermediate

.keywords: AO, index
.seealso: AOMappingHasPetscIndex(), AOCreateMapping()
@*/
int AOMappingHasApplicationIndex(AO ao, int idex, PetscTruth *hasIndex)
{
  AO_Mapping *aomap;
  int        *app;
  int         low, high, mid;

  PetscFunctionBegin;
  PetscValidHeaderSpecific(ao, AO_COOKIE);
  PetscValidPointer(hasIndex);
  aomap = (AO_Mapping *) ao->data;
  app   = aomap->app;
  /* Use bisection since the array is sorted */
  low  = 0;
  high = aomap->N - 1;
  while (low <= high) {
    mid = (low + high)/2;
    if (idex == app[mid]) {
      break;
    } else if (idex < app[mid]) {
      high = mid - 1;
    } else {
      low  = mid + 1;
    }
  }
  if (low > high) {
    *hasIndex = PETSC_FALSE;
  } else {
    *hasIndex = PETSC_TRUE;
  }
  PetscFunctionReturn(0);
}

#undef __FUNCT__  
#define __FUNCT__ "AOMappingHasPetscIndex"
/*@C
  AOMappingHasPetscIndex - Searches for the supplied petsc index.

  Input Parameters:
+ ao       - The AOMapping
- index    - The petsc index

  Output Parameter:
. hasIndex - Flag is PETSC_TRUE if the index exists

  Level: intermediate

.keywords: AO, index
.seealso: AOMappingHasApplicationIndex(), AOCreateMapping()
@*/
int AOMappingHasPetscIndex(AO ao, int idex, PetscTruth *hasIndex)
{
  AO_Mapping *aomap;
  int        *petsc;
  int         low, high, mid;

  PetscFunctionBegin;
  PetscValidHeaderSpecific(ao, AO_COOKIE);
  PetscValidPointer(hasIndex);
  aomap = (AO_Mapping *) ao->data;
  petsc = aomap->petsc;
  /* Use bisection since the array is sorted */
  low  = 0;
  high = aomap->N - 1;
  while (low <= high) {
    mid = (low + high)/2;
    if (idex == petsc[mid]) {
      break;
    } else if (idex < petsc[mid]) {
      high = mid - 1;
    } else {
      low  = mid + 1;
    }
  }
  if (low > high) {
    *hasIndex = PETSC_FALSE;
  } else {
    *hasIndex = PETSC_TRUE;
  }
  PetscFunctionReturn(0);
}

#undef __FUNCT__  
#define __FUNCT__ "AOCreateMapping"
/*@C
  AOCreateMapping - Creates a basic application mapping using two integer arrays.

  Input Parameters:
+ comm    - MPI communicator that is to share AO
. napp    - size of integer arrays
. myapp   - integer array that defines an ordering
- mypetsc - integer array that defines another ordering

  Output Parameter:
. aoout   - the new application mapping

  Options Database Key:
$ -ao_view : call AOView() at the conclusion of AOCreateMapping()

  Level: beginner

.keywords: AO, create
.seealso: AOCreateDebug(), AOCreateBasic(), AOCreateMappingIS(), AODestroy()
@*/
int AOCreateMapping(MPI_Comm comm, int napp, int *myapp, int *mypetsc, AO *aoout)
{
  AO          ao;
  AO_Mapping *aomap;
  int        *lens, *disp;
  int        *allpetsc,  *allapp;
  int        *petscPerm, *appPerm;
  int        *petsc;
  int         size, rank;
  int         N, start;
  int         i;
  PetscTruth  opt;
  int         ierr;

  PetscFunctionBegin;
  PetscValidPointer(aoout);
  *aoout = 0;
#ifndef PETSC_USE_DYNAMIC_LIBRARIES
  ierr = DMInitializePackage(PETSC_NULL);                                                                 CHKERRQ(ierr);
#endif

  PetscHeaderCreate(ao, _p_AO, struct _AOOps, AO_COOKIE, AO_MAPPING, "AO", comm, AODestroy, AOView);
  PetscLogObjectCreate(ao);
  ierr = PetscNew(AO_Mapping, &aomap);                                                                    CHKERRQ(ierr);
  PetscLogObjectMemory(ao, sizeof(struct _p_AO) + sizeof(AO_Mapping));
  ierr = PetscMemcpy(ao->ops, &AOps, sizeof(AOps));                                                       CHKERRQ(ierr);
  ierr = PetscStrallocpy(AO_SER_MAPPING_BINARY, &ao->serialize_name);                                     CHKERRQ(ierr);
  ao->data = (void *) aomap;

  /* transmit all lengths to all processors */
  ierr = MPI_Comm_size(comm, &size);                                                                      CHKERRQ(ierr);
  ierr = MPI_Comm_rank(comm, &rank);                                                                      CHKERRQ(ierr);
  ierr = PetscMalloc(2*size * sizeof(int), &lens);                                                        CHKERRQ(ierr);
  disp = lens + size;
  ierr = MPI_Allgather(&napp, 1, MPI_INT, lens, 1, MPI_INT, comm);                                        CHKERRQ(ierr);
  N    = 0;
  for(i = 0; i < size; i++) {
    disp[i] = N;
    N += lens[i];
  }
  aomap->N = N;
  ao->N    = N;
  ao->n    = N;

  /* If mypetsc is 0 then use "natural" numbering */
  if (!mypetsc) {
    start = disp[rank];
    ierr  = PetscMalloc((napp+1) * sizeof(int), &petsc);                                                  CHKERRQ(ierr);
    for(i = 0; i < napp; i++) {
      petsc[i] = start + i;
    }
  } else {
    petsc = mypetsc;
  }

  /* get all indices on all processors */
  ierr = PetscMalloc(N*4 * sizeof(int), &allapp);                                                         CHKERRQ(ierr);
  appPerm   = allapp   + N;
  allpetsc  = appPerm  + N;
  petscPerm = allpetsc + N;
  ierr = MPI_Allgatherv(myapp,   napp, MPI_INT, allapp,   lens, disp, MPI_INT, comm);                     CHKERRQ(ierr);
  ierr = MPI_Allgatherv(mypetsc, napp, MPI_INT, allpetsc, lens, disp, MPI_INT, comm);                     CHKERRQ(ierr);
  ierr = PetscFree(lens);                                                                                 CHKERRQ(ierr);

  /* generate a list of application and PETSc node numbers */
  ierr = PetscMalloc(N*4 * sizeof(int), &aomap->app);                                                     CHKERRQ(ierr);
  PetscLogObjectMemory(ao, 4*N * sizeof(int));
  aomap->appPerm   = aomap->app     + N;
  aomap->petsc     = aomap->appPerm + N;
  aomap->petscPerm = aomap->petsc   + N;
  for(i = 0; i < N; i++) {
    appPerm[i]   = i;
    petscPerm[i] = i;
  }
  ierr = PetscSortIntWithPermutation(N, allpetsc, petscPerm);                                             CHKERRQ(ierr);
  ierr = PetscSortIntWithPermutation(N, allapp,   appPerm);                                               CHKERRQ(ierr);
  /* Form sorted arrays of indices */
  for(i = 0; i < N; i++) {
    aomap->app[i]   = allapp[appPerm[i]];
    aomap->petsc[i] = allpetsc[petscPerm[i]];
  }
  /* Invert petscPerm[] into aomap->petscPerm[] */
  for(i = 0; i < N; i++) {
    aomap->petscPerm[petscPerm[i]] = i;
  }
  /* Form map between aomap->app[] and aomap->petsc[] */
  for(i = 0; i < N; i++) {
    aomap->appPerm[i] = aomap->petscPerm[appPerm[i]];
  }
  /* Invert appPerm[] into allapp[] */
  for(i = 0; i < N; i++) {
    allapp[appPerm[i]] = i;
  }
  /* Form map between aomap->petsc[] and aomap->app[] */
  for(i = 0; i < N; i++) {
    aomap->petscPerm[i] = allapp[petscPerm[i]];
  }
#ifdef PETSC_USE_BOPT_g
  /* Check that the permutations are complementary */
  for(i = 0; i < N; i++) {
    if (i != aomap->appPerm[aomap->petscPerm[i]])
      SETERRQ(PETSC_ERR_PLIB, "Invalid ordering");
  }
#endif
  /* Cleanup */
  if (!mypetsc) {
    ierr = PetscFree(petsc);                                                                              CHKERRQ(ierr);
  }
  ierr = PetscFree(allapp);                                                                               CHKERRQ(ierr);

  ierr = PetscOptionsHasName(PETSC_NULL, "-ao_view", &opt);                                               CHKERRQ(ierr);
  if (opt == PETSC_TRUE) {
    ierr = AOView(ao, PETSC_VIEWER_STDOUT_SELF);                                                          CHKERRQ(ierr);
  }

  *aoout = ao;
  PetscFunctionReturn(0);
}

#undef __FUNCT__  
#define __FUNCT__ "AOCreateMappingIS"
/*@C
  AOCreateMappingIS - Creates a basic application ordering using two index sets.

  Input Parameters:
+ comm    - MPI communicator that is to share AO
. isapp   - index set that defines an ordering
- ispetsc - index set that defines another ordering

  Output Parameter:
. aoout   - the new application ordering

  Options Database Key:
$ -ao_view : call AOView() at the conclusion of AOCreateMappingIS()

  Level: beginner

.keywords: AO, create
.seealso: AOCreateBasic(), AOCreateMapping(), AODestroy()
@*/
int AOCreateMappingIS(IS isapp, IS ispetsc, AO *aoout)
{
  MPI_Comm comm;
  int     *mypetsc, *myapp;
  int      napp, npetsc;
  int      ierr;

  PetscFunctionBegin;
  ierr = PetscObjectGetComm((PetscObject) isapp, &comm);                                                  CHKERRQ(ierr);
  ierr = ISGetSize(isapp, &napp);                                                                         CHKERRQ(ierr);
  if (ispetsc) {
    ierr = ISGetSize(ispetsc, &npetsc);                                                                   CHKERRQ(ierr);
    if (napp != npetsc) SETERRQ(PETSC_ERR_ARG_SIZ, "Local IS lengths must match");
    ierr = ISGetIndices(ispetsc, &mypetsc);                                                               CHKERRQ(ierr);
  }
  ierr = ISGetIndices(isapp, &myapp);                                                                     CHKERRQ(ierr);

  ierr = AOCreateMapping(comm, napp, myapp, mypetsc, aoout);                                              CHKERRQ(ierr);

  ierr = ISRestoreIndices(isapp, &myapp);                                                                 CHKERRQ(ierr);
  if (ispetsc) {
    ierr = ISRestoreIndices(ispetsc, &mypetsc);                                                           CHKERRQ(ierr);
  }
  PetscFunctionReturn(0);
}
