/* creates CVODES forward sensitivity solver structures
   return 1 => success
   return 0 => failure
*/
int
IntegratorInstance_createCVODESSolverStructures(integratorInstance_t *engine)
{
    int i, j, flag, neq, ns;
    realtype *ydata, *abstoldata, *ySdata, *senstoldata;

    odeModel_t *om = engine->om;
    cvodeData_t *data = engine->data;
    cvodeSolver_t *solver = engine->solver;
    cvodeSettings_t *opt = engine->opt;

    /* realtype pbar[data->nsens+1]; */
    /*int *plist; removed by AMF 8/11/05
    realtype *pbar;

    ASSIGN_NEW_MEMORY_BLOCK(plist, data->nsens+1, int, 0)
    ASSIGN_NEW_MEMORY_BLOCK(pbar, data->nsens+1, realtype, 0)*/


    /*****  adding sensitivity specific structures ******/

    /**
     * construct sensitivity related structures
     */
    /* free sensitivity from former runs (changed for non-default cases!) */
    ODEModel_freeSensitivity(om);

    /* if jacobian matrix has been constructed successfully,
       construct sensitivity matrix dx/dp, sets om->sensitivity
       to 1 if successful, 0 otherwise */
    /*!!! this function will require additional input for
       non-default case, via sensitivity input settings! !!!*/

    if ( om->jacobian ) 
      ODEModel_constructSensitivity(om);
    else {
      om->sensitivity = 0;
      om->jacob_sens = NULL;
      om->nsens = om->nconst;

      ASSIGN_NEW_MEMORY_BLOCK(om->index_sens, om->nsens, int, 0);
      /*!!! non-default case:
	these values should be passed for other cases !!!*/
      for ( i=0; i<om->nsens; i++ )
	om->index_sens[i] = om->neq + om->nass + i;
    }
    
    engine->solver->nsens = data->nsens;

    solver->yS = N_VNewVectorArray_Serial(data->nsens, data->neq);      
    if (check_flag((void *)solver->yS, "N_VNewVectorArray_Serial",
		   1, stderr))
      return(0);

    /*
     * (re)initialize ySdata sensitivities
     */
    /* absolute tolerance for sensitivity error control */
    solver->senstol = N_VNew_Serial(data->nsens);
    abstoldata = NV_DATA_S(solver->senstol);
    for ( j=0; j<data->nsens; j++ ) {
      abstoldata[j] = 1e-4;
      ySdata = NV_DATA_S(solver->yS[j]);
      for ( i=0; i<data->neq; i++ ) 
	ySdata[i] = data->sensitivity[i][j];
    }

    /*
     * set method
     */
    if ( opt->SensMethod == 0 ) 
      flag =CVodeSensMalloc(solver->cvode_mem,data->nsens,
			    CV_SIMULTANEOUS, solver->yS);
    else if ( opt->SensMethod == 1 )
      flag = CVodeSensMalloc(solver->cvode_mem, data->nsens,
			     CV_STAGGERED, solver->yS);
    else if ( opt->SensMethod == 2 )
      flag = CVodeSensMalloc(solver->cvode_mem, data->nsens,
			     CV_STAGGERED1, solver->yS);
    if(check_flag(&flag, "CVodeSensMalloc", 1, stderr)) {
      return 0;
      /* ERROR HANDLING CODE if failes */
    }

    /* *** set parameter values or R.H.S function fS *****/
    /* NOTES: */
    /* !!! plist could later be used to specify requested parameters
       for sens.analysis !!! */
    
    /* was construction of Jacobian and
       parametric matrix successfull ? */
    if ( om->sensitivity && om->jacobian ) {
      flag = CVodeSetSensRhs1Fn(solver->cvode_mem, fS);
      if (check_flag(&flag, "CVodeSetSensRhs1Fn", 1, stderr)) {
	return 0;
	/* ERROR HANDLING CODE if failes */
      }
      flag = CVodeSetSensFdata(solver->cvode_mem, data);
      if (check_flag(&flag, "CVodeSetSensFdata", 1, stderr))  {
	return 0;
	/* ERROR HANDLING CODE if  failes */
      }  
      data->p = NULL;
    }
    else {
      ASSIGN_NEW_MEMORY_BLOCK(data->p, data->nsens, realtype, 0);
      for ( i=0; i<data->nsens; i++ ) {
        /* data->p is only required if R.H.S. fS cannot be supplied */
	/* plist[i] = i+1; */
	data->p[i] = data->value[om->index_sens[i]]; 
	/* pbar[i] = abs(data->p[i]);  */ /*??? WHAT IS PBAR ???*/ 
      }
      flag = CVodeSetSensParams(solver->cvode_mem, data->p, NULL, NULL);
      if (check_flag(&flag, "CVodeSetSensParams", 1, stderr))  {
	return 0;
	/* ERROR HANDLING CODE if  failes */
      }
      flag = CVodeSetSensRho(solver->cvode_mem, 0.0); /* what is it? */
      if (check_flag(&flag, "CVodeSetSensRhs1Fn", 1, stderr)) {
	/* ERROR HANDLING CODE if  failes */
	return 0;
      }
    }
/*     CVodeSetSensTolerances(solver->cvode_mem, CV_SS, */
/* 			   solver->reltol, &solver->senstol); */
    
    /* difference FALSE/TRUE ? */
    flag = CVodeSetSensErrCon(solver->cvode_mem, FALSE);
    if (check_flag(&flag, "CVodeSetSensFdata", 1, stderr)) {
      return 0;
      /* ERROR HANDLING CODE if failes */
    } 
    
    return 1; /* OK */
}
int main(int argc, char *argv[])
{
  void *cvode_mem;
  UserData data;
  realtype abstol, reltol, t, tout;
  N_Vector y;
  int iout, flag;

  realtype *pbar;
  int is, *plist;
  N_Vector *uS;
  booleantype sensi, err_con;
  int sensi_meth;

  pbar = NULL;
  plist = NULL;
  uS = NULL;
  y = NULL;
  data = NULL;
  cvode_mem = NULL;

  /* Process arguments */
  ProcessArgs(argc, argv, &sensi, &sensi_meth, &err_con);

  /* Problem parameters */
  data = AllocUserData();
  if(check_flag((void *)data, "AllocUserData", 2)) return(1);
  InitUserData(data);

  /* Initial states */
  y = N_VNew_Serial(NEQ);
  if(check_flag((void *)y, "N_VNew_Serial", 0)) return(1);
  SetInitialProfiles(y, data->dx, data->dz);
  
  /* Tolerances */
  abstol=ATOL; 
  reltol=RTOL;

  /* Create CVODES object */
  cvode_mem = CVodeCreate(CV_BDF, CV_NEWTON);
  if(check_flag((void *)cvode_mem, "CVodeCreate", 0)) return(1);

  flag = CVodeSetFdata(cvode_mem, data);
  if(check_flag(&flag, "CVodeSetFdata", 1)) return(1);

  flag = CVodeSetMaxNumSteps(cvode_mem, 2000);
  if(check_flag(&flag, "CVodeSetMaxNumSteps", 1)) return(1);

  /* Allocate CVODES memory */
  flag = CVodeMalloc(cvode_mem, f, T0, y, CV_SS, reltol, &abstol);
  if(check_flag(&flag, "CVodeMalloc", 1)) return(1);

  /* Attach CVSPGMR linear solver */
  flag = CVSpgmr(cvode_mem, PREC_LEFT, 0);
  if(check_flag(&flag, "CVSpgmr", 1)) return(1);

  flag = CVSpilsSetPreconditioner(cvode_mem, Precond, PSolve, data);
  if(check_flag(&flag, "CVSpilsSetPreconditioner", 1)) return(1);

  printf("\n2-species diurnal advection-diffusion problem\n");

  /* Forward sensitivity analysis */
  if(sensi) {

    plist = (int *) malloc(NS * sizeof(int));
    if(check_flag((void *)plist, "malloc", 2)) return(1);
    for(is=0; is<NS; is++) plist[is] = is;

    pbar = (realtype *) malloc(NS * sizeof(realtype));
    if(check_flag((void *)pbar, "malloc", 2)) return(1);
    for(is=0; is<NS; is++) pbar[is] = data->p[plist[is]];

    uS = N_VCloneVectorArray_Serial(NS, y);
    if(check_flag((void *)uS, "N_VCloneVectorArray_Serial", 0)) return(1);
    for(is=0;is<NS;is++)
      N_VConst(ZERO,uS[is]);

    flag = CVodeSensMalloc(cvode_mem, NS, sensi_meth, uS);
    if(check_flag(&flag, "CVodeSensMalloc", 1)) return(1);

    flag = CVodeSetSensErrCon(cvode_mem, err_con);
    if(check_flag(&flag, "CVodeSetSensErrCon", 1)) return(1);

    flag = CVodeSetSensRho(cvode_mem, ZERO);
    if(check_flag(&flag, "CVodeSetSensRho", 1)) return(1);

    flag = CVodeSetSensParams(cvode_mem, data->p, pbar, plist);
    if(check_flag(&flag, "CVodeSetSensParams", 1)) return(1);

    printf("Sensitivity: YES ");
    if(sensi_meth == CV_SIMULTANEOUS)   
      printf("( SIMULTANEOUS +");
    else 
      if(sensi_meth == CV_STAGGERED) printf("( STAGGERED +");
      else                           printf("( STAGGERED1 +");   
    if(err_con) printf(" FULL ERROR CONTROL )");
    else        printf(" PARTIAL ERROR CONTROL )");
    
  } else {

    printf("Sensitivity: NO ");

  }

  /* In loop over output points, call CVode, print results, test for error */

  printf("\n\n");
  printf("========================================================================\n");
  printf("     T     Q       H      NST                    Bottom left  Top right \n");
  printf("========================================================================\n");

  for (iout=1, tout = TWOHR; iout <= NOUT; iout++, tout += TWOHR) {
    flag = CVode(cvode_mem, tout, y, &t, CV_NORMAL);
    if(check_flag(&flag, "CVode", 1)) break;
    PrintOutput(cvode_mem, t, y);
    if (sensi) {
      flag = CVodeGetSens(cvode_mem, t, uS);
      if(check_flag(&flag, "CVodeGetSens", 1)) break;
      PrintOutputS(uS);
    }
    
    printf("------------------------------------------------------------------------\n");

  }

  /* Print final statistics */
  PrintFinalStats(cvode_mem, sensi);

  /* Free memory */
  N_VDestroy_Serial(y);
  if (sensi) {
    N_VDestroyVectorArray_Serial(uS, NS);
    free(pbar);
    free(plist);
  }
  FreeUserData(data);
  CVodeFree(&cvode_mem);

  return(0);
}