#ifndef __PAR_EQUILIBRIUM_IC_H__
#define __PAR_EQUILIBRIUM_IC_H__

#include "GAMER.h"
#include "TestProb.h"
#include "vector"
#include <iostream>
#include <fstream>
#include <sstream>
using namespace std;

//gsl library
#ifdef SUPPORT_GSL
#include <gsl/gsl_integration.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_roots.h>
#endif

typedef struct Filename_Parameter{
   int Cloud_Num;
   vector<string> Params_Filenames;
}FP;
typedef struct Physical_Parameter{
   int      Cloud_Num;

   string Params_Filenames;

   char     Cloud_Type[MAX_STRING];
   char     Density_Table_Name[MAX_STRING];
   int      AddExtPot;
   char     ExtPot_Table_Name[MAX_STRING];
   int      Cloud_RSeed;
   double   Cloud_Rho0;
   double   Cloud_R0;
   double   Cloud_MaxR;
   double*  Cloud_Center;
   double*  Cloud_BulkVel;
   double   Cloud_Einasto_Power_Factor;
   double   Cloud_Par_Num;

   int      Cloud_MassProfNBin;
}PhysP;

class Par_EquilibriumIC
{
   public:
      Par_EquilibriumIC();
      virtual ~Par_EquilibriumIC();
      void Read_Filenames( const char *filename_para);
      void Load_Physical_Params(const FP filenames,const int cloud_idx, const long NPar_AllRank);
      void Init();
      void Par_SetEquilibriumIC(real *Mass_AllRank, real *Pos_AllRank[3], real *Vel_AllRank[3],const long Par_Idx);

      PhysP params;
      FP   filenames;
   protected:

   private:
      // Derive physical attributes for particles
      double Set_Mass( double x );
      double Set_Density( double x );
      double Set_Velocity(const double x);

      // Initialize physical parameter tables
      void Init_Mass();
      void Init_Pot();
      void Init_Prob_Dens();

      //  Initialization through Table
      void Init_Mass_Table();
      void Init_Pot_Table();

      //  Add External Potential
      void Add_Ext_Pot();

      // Auxiliary functions
      int Aux_CountRow( const char *filename );
      int Aux_Countcolumn( const char *filename );
      int GetParams( const char *filename,const char *keyword,const int para_num,const char *para_type,vector <string> &container);
      void Check_InputFileName();
      void RanVec_FixRadius( const double r, double RanVec[] );

      // Solve Eddington's equation
      double potential(const double x);
      double inverse_psi_to_index (double psi);
      double integration_eng_base(double eng);

      double delta;
      double eng_min;
      double *prob_dens;
      double *int_prob_dens;
      double *psi;

      // statistics
      double slope(double* a,double* b,int start,int fin);
      void smooth_all(double* x,int start,int fin);
      double ave(double* a,int start,int fin);
      double var_n(double* a,int start,int fin);
      double cor(double* x,double* y,int start,int fin);
      void mask(double* x,int start,int fin);
      void add_num(double* x,int start,int fin);

      // Tables of particles' attributes
      double *Table_r;
      double *Table_Enclosed_Mass;
      double *Table_Density;
      double *Table_dRho_dr;
      double *Table_dRho_dx;
      double *Table_Gravity_Field;
      double *Table_Gravity_Potential;

      // Random number generator
      RandomNumber_t *Random_Num_Gen ;
};

#endif //__PAR_EQUILIBRIUM_IC_H__


/* specfunc/bessel_zero.c
 *
 * Copyright (C) 1996, 1997, 1998, 1999, 2000 Gerard Jungman
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

/* Author:  G. Jungman */

#include <config.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_sf_airy.h>
#include <gsl/gsl_sf_pow_int.h>
#include <gsl/gsl_sf_bessel.h>

#include "error.h"

#include "bessel_olver.h"

/* For Chebyshev expansions of the roots as functions of nu,
 * see [G. Nemeth, Mathematical Approximation of Special Functions].
 * This gives the fits for all nu and s <= 10.
 * I made the fits for other values of s myself [GJ].
 */

/* Chebyshev expansion: j_{nu,1} = c_k T_k*(nu/2), nu <= 2 */
static const double coef_jnu1_a[] = {
  3.801775243633476,
  1.360704737511120,
 -0.030707710261106,
  0.004526823746202,
 -0.000808682832134,
  0.000159218792489,
 -0.000033225189761,
  0.000007205599763,
 -0.000001606110397,
  0.000000365439424,
 -0.000000084498039,
  0.000000019793815,
 -0.000000004687054,
  0.000000001120052,
 -0.000000000269767,
  0.000000000065420,
 -0.000000000015961,
  0.000000000003914,
 -0.000000000000965,
  0.000000000000239,
 -0.000000000000059,
  0.000000000000015,
 -0.000000000000004,
  0.000000000000001
};

/* Chebyshev expansion: j_{nu,1} = nu c_k T_k*((2/nu)^(2/3)), nu >= 2 */
static const double coef_jnu1_b[] = {
  1.735063412537096,
  0.784478100951978,
  0.048881473180370,
 -0.000578279783021,
 -0.000038984957864,
  0.000005758297879,
 -0.000000327583229,
 -0.000000003853878,
  0.000000002284653,
 -0.000000000153079,
 -0.000000000000895,
  0.000000000000283,
  0.000000000000043,
  0.000000000000010,
 -0.000000000000003
};

/* Chebyshev expansion: j_{nu,2} = c_k T_k*(nu/2), nu <= 2 */
static const double coef_jnu2_a[] = {
  6.992370244046161,
  1.446379282056534,
 -0.023458616207293,
  0.002172149448700,
 -0.000246262775620,
  0.000030990180959,
 -0.000004154183047,
  0.000000580766328,
 -0.000000083648175,
  0.000000012317355,
 -0.000000001844887,
  0.000000000280076,
 -0.000000000042986,
  0.000000000006658,
 -0.000000000001039,
  0.000000000000163,
 -0.000000000000026,
  0.000000000000004,
 -0.000000000000001
};

/* Chebyshev expansion: j_{nu,2} = nu c_k T_k*((2/nu)^(2/3)), nu >= 2 */
static const double coef_jnu2_b[] = {
  2.465611864263400,
  1.607952988471069,
  0.138758034431497,
 -0.003687791182054,
 -0.000051276007868,
  0.000045113570749,
 -0.000007579172152,
  0.000000736469208,
 -0.000000011118527,
 -0.000000011919884,
  0.000000002696788,
 -0.000000000314488,
  0.000000000008124,
  0.000000000005211,
 -0.000000000001292,
  0.000000000000158,
 -0.000000000000004,
 -0.000000000000003,
  0.000000000000001
};

/* Chebyshev expansion: j_{nu,3} = c_k T_k*(nu/3), nu <= 3 */
static const double coef_jnu3_a[] = {
  10.869647065239236,
   2.177524286141710,
  -0.034822817125293,
   0.003167249102413,
  -0.000353960349344,
   0.000044039086085,
  -0.000005851380981,
   0.000000812575483,
  -0.000000116463617,
   0.000000017091246,
  -0.000000002554376,
   0.000000000387335,
  -0.000000000059428,
   0.000000000009207,
  -0.000000000001438,
   0.000000000000226,
  -0.000000000000036,
   0.000000000000006,
  -0.000000000000001
};

/* Chebyshev expansion: j_{nu,3} = nu c_k T_k*((3/nu)^(2/3)), nu >= 3 */
static const double coef_jnu3_b[] = {
  2.522816775173244,
  1.673199424973720,
  0.146431617506314,
 -0.004049001763912,
 -0.000039517767244,
  0.000048781729288,
 -0.000008729705695,
  0.000000928737310,
 -0.000000028388244,
 -0.000000012927432,
  0.000000003441008,
 -0.000000000471695,
  0.000000000025590,
  0.000000000005502,
 -0.000000000001881,
  0.000000000000295,
 -0.000000000000020,
 -0.000000000000003,
  0.000000000000001
};

/* Chebyshev expansion: j_{nu,4} = c_k T_k*(nu/4), nu <= 4 */
static const double coef_jnu4_a[] = {
  14.750310252773009,
   2.908010932941708,
  -0.046093293420315,
   0.004147172321412,
  -0.000459092310473,
   0.000056646951906,
  -0.000007472351546,
   0.000001031210065,
  -0.000000147008137,
   0.000000021475218,
  -0.000000003197208,
   0.000000000483249,
  -0.000000000073946,
   0.000000000011431,
  -0.000000000001782,
   0.000000000000280,
  -0.000000000000044,
   0.000000000000007,
  -0.000000000000001
};

/* Chebyshev expansion: j_{nu,4} = nu c_k T_k*((4/nu)^(2/3)), nu >= 4 */
static const double coef_jnu4_b[] = {
  2.551681323117914,
  1.706177978336572,
  0.150357658406131,
 -0.004234001378590,
 -0.000033854229898,
  0.000050763551485,
 -0.000009337464057,
  0.000001029717834,
 -0.000000037474196,
 -0.000000013450153,
  0.000000003836180,
 -0.000000000557404,
  0.000000000035748,
  0.000000000005487,
 -0.000000000002187,
  0.000000000000374,
 -0.000000000000031,
 -0.000000000000003,
  0.000000000000001
};

/* Chebyshev expansion: j_{nu,5} = c_k T_k*(nu/5), nu <= 5 */
static const double coef_jnu5_a[] = {
  18.632261081028211,
   3.638249012596966,
  -0.057329705998828,
   0.005121709126820,
  -0.000563325259487,
   0.000069100826174,
  -0.000009066603030,
   0.000001245181383,
  -0.000000176737282,
   0.000000025716695,
  -0.000000003815184,
   0.000000000574839,
  -0.000000000087715,
   0.000000000013526,
  -0.000000000002104,
   0.000000000000330,
  -0.000000000000052,
   0.000000000000008,
  -0.000000000000001
};

/* Chebyshev expansion: j_{nu,5} = nu c_k T_k*((5/nu)^(2/3)), nu >= 5 */
/* FIXME: There is something wrong with this fit, in about the
 * 9th or 10th decimal place.
 */
static const double coef_jnu5_b[] = {
  2.569079487591442,
  1.726073360882134,
  0.152740776809531,
 -0.004346449660148,
 -0.000030512461856,
  0.000052000821080,
 -0.000009713343981,
  0.000001091997863,
 -0.000000043061707,
 -0.000000013779413,
  0.000000004082870,
 -0.000000000611259,
  0.000000000042242,
  0.000000000005448,
 -0.000000000002377,
  0.000000000000424,
 -0.000000000000038,
 -0.000000000000002,
  0.000000000000002
};

/* Chebyshev expansion: j_{nu,6} = c_k T_k*(nu/6), nu <= 6 */
static const double coef_jnu6_a[] = {
  22.514836143374042,
   4.368367257557198,
  -0.068550155285562,
   0.006093776505822,
  -0.000667152784957,
   0.000081486022398,
  -0.000010649011647,
   0.000001457089679,
  -0.000000206105082,
   0.000000029894724,
  -0.000000004422012,
   0.000000000664471,
  -0.000000000101140,
   0.000000000015561,
  -0.000000000002416,
   0.000000000000378,
  -0.000000000000060,
   0.000000000000009,
  -0.000000000000002
};

/* Chebyshev expansion: j_{nu,6} = nu c_k T_k*((6/nu)^(2/3)), nu >= 6 */
static const double coef_jnu6_b[] = {
  2.580710285494837,
  1.739380728566154,
  0.154340696401691,
 -0.004422028860168,
 -0.000028305272624,
  0.000052845975269,
 -0.000009968794373,
  0.000001134252926,
 -0.000000046841241,
 -0.000000014007555,
  0.000000004251816,
 -0.000000000648213,
  0.000000000046728,
  0.000000000005414,
 -0.000000000002508,
  0.000000000000459,
 -0.000000000000043,
 -0.000000000000002,
  0.000000000000002
};

/* Chebyshev expansion: j_{nu,7} = c_k T_k*(nu/7), nu <= 7 */
static const double coef_jnu7_a[] = {
  26.397760539730869,
   5.098418721711790,
  -0.079761896398948,
   0.007064521280487,
  -0.000770766522482,
   0.000093835449636,
  -0.000012225308542,
   0.000001667939800,
  -0.000000235288157,
   0.000000034040347,
  -0.000000005023142,
   0.000000000753101,
  -0.000000000114389,
   0.000000000017564,
  -0.000000000002722,
   0.000000000000425,
  -0.000000000000067,
   0.000000000000011,
  -0.000000000000002
};

/* Chebyshev expansion: j_{nu,7} = nu c_k T_k*((7/nu)^(2/3)), nu >= 7 */
static const double coef_jnu7_b[] = {
  2.589033335856773,
  1.748907007612678,
  0.155488900387653,
 -0.004476317805688,
 -0.000026737952924,
  0.000053459680946,
 -0.000010153699240,
  0.000001164804272,
 -0.000000049566917,
 -0.000000014175403,
  0.000000004374840,
 -0.000000000675135,
  0.000000000050004,
  0.000000000005387,
 -0.000000000002603,
  0.000000000000485,
 -0.000000000000047,
 -0.000000000000002,
  0.000000000000002
};

/* Chebyshev expansion: j_{nu,8} = c_k T_k*(nu/8), nu <= 8 */
static const double coef_jnu8_a[] = {
  30.280900001606662,
   5.828429205461221,
  -0.090968381181069,
   0.008034479731033,
  -0.000874254899080,
   0.000106164151611,
  -0.000013798098749,
   0.000001878187386,
  -0.000000264366627,
   0.000000038167685,
  -0.000000005621060,
   0.000000000841165,
  -0.000000000127538,
   0.000000000019550,
  -0.000000000003025,
   0.000000000000472,
  -0.000000000000074,
   0.000000000000012,
  -0.000000000000002
};

/* Chebyshev expansion: j_{nu,8} = nu c_k T_k*((8/nu)^(2/3)), nu >= 8 */
static const double coef_jnu8_b[] = {
  2.595283877150078,
  1.756063044986928,
  0.156352972371030,
 -0.004517201896761,
 -0.000025567187878,
  0.000053925472558,
 -0.000010293734486,
  0.000001187923085,
 -0.000000051625122,
 -0.000000014304212,
  0.000000004468450,
 -0.000000000695620,
  0.000000000052500,
  0.000000000005367,
 -0.000000000002676,
  0.000000000000505,
 -0.000000000000050,
 -0.000000000000002,
  0.000000000000002
};

/* Chebyshev expansion: j_{nu,9} = c_k T_k*(nu/9), nu <= 9 */
static const double coef_jnu9_a[] = {
  34.164181213238386,
   6.558412747925228,
  -0.102171455365016,
   0.009003934361201,
  -0.000977663914535,
   0.000118479876579,
  -0.000015368714220,
   0.000002088064285,
  -0.000000293381154,
   0.000000042283900,
  -0.000000006217033,
   0.000000000928887,
  -0.000000000140627,
   0.000000000021526,
  -0.000000000003326,
   0.000000000000518,
  -0.000000000000081,
   0.000000000000013,
  -0.000000000000002
};

/* Chebyshev expansion: j_{nu,9} = nu c_k T_k*((9/nu)^(2/3)), nu >= 9 */
static const double coef_jnu9_b[] = {
  2.600150240905079,
  1.761635491694032,
  0.157026743724010,
 -0.004549100368716,
 -0.000024659248617,
  0.000054291035068,
 -0.000010403464334,
  0.000001206027524,
 -0.000000053234089,
 -0.000000014406241,
  0.000000004542078,
 -0.000000000711728,
  0.000000000054464,
  0.000000000005350,
 -0.000000000002733,
  0.000000000000521,
 -0.000000000000052,
 -0.000000000000002,
  0.000000000000002
};

/* Chebyshev expansion: j_{nu,10} = c_k T_k*(nu/10), nu <= 10 */
static const double coef_jnu10_a[] = {
  38.047560766184647,
   7.288377637926008,
  -0.113372193277897,
   0.009973047509098,
  -0.001081019701335,
   0.000130786983847,
  -0.000016937898538,
   0.000002297699179,
  -0.000000322354218,
   0.000000046392941,
  -0.000000006811759,
   0.000000001016395,
  -0.000000000153677,
   0.000000000023486,
  -0.000000000003616,
   0.000000000000561,
  -0.000000000000095,
   0.000000000000027,
  -0.000000000000013,
   0.000000000000005
};

/* Chebyshev expansion: j_{nu,10} = nu c_k T_k*((10/nu)^(2/3)), nu >= 10 */
static const double coef_jnu10_b[] = {
  2.604046346867949,
  1.766097596481182,
  0.157566834446511,
 -0.004574682244089,
 -0.000023934500688,
  0.000054585558231,
 -0.000010491765415,
  0.000001220589364,
 -0.000000054526331,
 -0.000000014489078,
  0.000000004601510,
 -0.000000000724727,
  0.000000000056049,
  0.000000000005337,
 -0.000000000002779,
  0.000000000000533,
 -0.000000000000054,
 -0.000000000000002,
  0.000000000000002
};

/* Chebyshev expansion: j_{nu,11} = c_k T_k*(nu/22), nu <= 22 */
static const double coef_jnu11_a[] = {
  49.5054081076848637,
  15.33692279367165101,
 -0.33677234163517130,
  0.04623235772920729,
 -0.00781084960665093,
  0.00147217395434708,
 -0.00029695043846867,
  0.00006273356860235,
 -0.00001370575125628,
  3.07171282012e-6,
 -7.0235041249e-7,
  1.6320559339e-7,
 -3.843117306e-8,
  9.15083800e-9,
 -2.19957642e-9,
  5.3301703e-10,
 -1.3007541e-10,
  3.193827e-11,
 -7.88605e-12,
  1.95918e-12,
 -4.9020e-13,
  1.2207e-13,
 -2.820e-14,
  5.25e-15,
 -1.88e-15,
  2.80e-15,
 -2.45e-15
};

/* Chebyshev expansion: j_{nu,12} = c_k T_k*(nu/24), nu <= 24 */
static const double coef_jnu12_a[] = {
  54.0787833216641519,
  16.7336367772863598,
 -0.36718411124537953,
  0.05035523375053820,
 -0.00849884978867533,
  0.00160027692813434,
 -0.00032248114889921,
  0.00006806354127199,
 -0.00001485665901339,
  3.32668783672e-6,
 -7.5998952729e-7,
  1.7644939709e-7,
 -4.151538210e-8,
  9.87722772e-9,
 -2.37230133e-9,
  5.7442875e-10,
 -1.4007767e-10,
  3.437166e-11,
 -8.48215e-12,
  2.10554e-12,
 -5.2623e-13,
  1.3189e-13,
 -3.175e-14,
  5.73e-15,
  5.6e-16,
 -8.7e-16,
 -6.5e-16
};

/* Chebyshev expansion: j_{nu,13} = c_k T_k*(nu/26), nu <= 26 */
static const double coef_jnu13_a[] = {
  58.6521941921708890,
  18.1303398137970284,
 -0.39759381380126650,
  0.05447765240465494,
 -0.00918674227679980,
  0.00172835361420579,
 -0.00034800528297612,
  0.00007339183835188,
 -0.00001600713368099,
  3.58154960392e-6,
 -8.1759873497e-7,
  1.8968523220e-7,
 -4.459745253e-8,
  1.060304419e-8,
 -2.54487624e-9,
  6.1580214e-10,
 -1.5006751e-10,
  3.679707e-11,
 -9.07159e-12,
  2.24713e-12,
 -5.5943e-13,
  1.4069e-13,
 -3.679e-14,
  1.119e-14,
 -4.99e-15,
  3.43e-15,
 -2.85e-15,
  2.3e-15,
 -1.7e-15,
  8.7e-16
};

/* Chebyshev expansion: j_{nu,14} = c_k T_k*(nu/28), nu <= 28 */
static const double coef_jnu14_a[] = {
  63.2256329577315566,
  19.5270342832914901,
 -0.42800190567884337,
  0.05859971627729398,
 -0.00987455163523582,
  0.00185641011402081,
 -0.00037352439419968,
  0.00007871886257265,
 -0.00001715728110045,
  3.83632624437e-6,
 -8.7518558668e-7,
  2.0291515353e-7,
 -4.767795233e-8,
  1.132844415e-8,
 -2.71734219e-9,
  6.5714886e-10,
 -1.6005342e-10,
  3.922557e-11,
 -9.66637e-12,
  2.39379e-12,
 -5.9541e-13,
  1.4868e-13,
 -3.726e-14,
  9.37e-15,
 -2.36e-15,
  6.0e-16
};

/* Chebyshev expansion: j_{nu,15} = c_k T_k*(nu/30), nu <= 30 */
static const double coef_jnu15_a[] = {
  67.7990939565631635,
  20.9237219226859859,
 -0.45840871823085836,
  0.06272149946755639,
 -0.01056229551143042,
  0.00198445078693100,
 -0.00039903958650729,
  0.00008404489865469,
 -0.00001830717574922,
  4.09103745566e-6,
 -9.3275533309e-7,
  2.1614056403e-7,
 -5.075725222e-8,
  1.205352081e-8,
 -2.88971837e-9,
  6.9846848e-10,
 -1.7002946e-10,
  4.164941e-11,
 -1.025859e-11,
  2.53921e-12,
 -6.3128e-13,
  1.5757e-13,
 -3.947e-14,
  9.92e-15,
 -2.50e-15,
  6.3e-16
};

/* Chebyshev expansion: j_{nu,16} = c_k T_k*(nu/32), nu <= 32 */
static const double coef_jnu16_a[] = {
  72.3725729616724770,
  22.32040402918608585,
 -0.48881449782358690,
  0.06684305681828766,
 -0.01124998690363398,
  0.00211247882775445,
 -0.00042455166484632,
  0.00008937015316346,
 -0.00001945687139551,
  4.34569739281e-6,
 -9.9031173548e-7,
  2.2936247195e-7,
 -5.383562595e-8,
  1.277835103e-8,
 -3.06202860e-9,
  7.3977037e-10,
 -1.8000071e-10,
  4.407196e-11,
 -1.085046e-11,
  2.68453e-12,
 -6.6712e-13,
  1.6644e-13,
 -4.168e-14,
  1.047e-14,
 -2.64e-15,
  6.7e-16
};

/* Chebyshev expansion: j_{nu,17} = c_k T_k*(nu/34), nu <= 34 */
static const double coef_jnu17_a[] = {
  76.9460667535209549,
  23.71708159112252670,
 -0.51921943142405352,
  0.07096442978067622,
 -0.01193763559341369,
  0.00224049662974902,
 -0.00045006122941781,
  0.00009469477941684,
 -0.00002060640777107,
  4.60031647195e-6,
 -1.04785755046e-6,
  2.4258161247e-7,
 -5.691327087e-8,
  1.350298805e-8,
 -3.23428733e-9,
  7.8105847e-10,
 -1.8996825e-10,
  4.649350e-11,
 -1.144205e-11,
  2.82979e-12,
 -7.0294e-13,
  1.7531e-13,
 -4.388e-14,
  1.102e-14,
 -2.78e-15,
  7.0e-16
};

/* Chebyshev expansion: j_{nu,18} = c_k T_k*(nu/36), nu <= 36 */
static const double coef_jnu18_a[] = {
  81.5195728368096659,
  25.11375537470259305,
 -0.54962366347317668,
  0.07508565026117689,
 -0.01262524908033818,
  0.00236850602019778,
 -0.00047556873651929,
  0.00010001889347161,
 -0.00002175581482429,
  4.85490251239e-6,
 -1.10539483940e-6,
  2.5579853343e-7,
 -5.999033352e-8,
  1.422747129e-8,
 -3.40650521e-9,
  8.2233565e-10,
 -1.9993286e-10,
  4.891426e-11,
 -1.203343e-11,
  2.97498e-12,
 -7.3875e-13,
  1.8418e-13,
 -4.608e-14,
  1.157e-14,
 -2.91e-15,
  7.4e-16
};

/* Chebyshev expansion: j_{nu,19} = c_k T_k*(nu/38), nu <= 38 */
static const double coef_jnu19_a[] = {
  86.0930892477047512,
  26.51042598308271729,
 -0.58002730731948358,
  0.07920674321589394,
 -0.01331283320930301,
  0.00249650841778073,
 -0.00050107453900793,
  0.00010534258471335,
 -0.00002290511552874,
  5.10946148897e-6,
 -1.16292517157e-6,
  2.6901365037e-7,
 -6.306692473e-8,
  1.495183048e-8,
 -3.57869025e-9,
  8.6360410e-10,
 -2.0989514e-10,
  5.133439e-11,
 -1.262465e-11,
  3.12013e-12,
 -7.7455e-13,
  1.9304e-13,
 -4.829e-14,
  1.212e-14,
 -3.05e-15,
  7.7e-16
};

/* Chebyshev expansion: j_{nu,20} = c_k T_k*(nu/40), nu <= 40 */
static const double coef_jnu20_a[] = {
  90.6666144195163770,
  27.9070938975436823,
 -0.61043045315390591,
  0.08332772844325554,
 -0.01400039260208282,
  0.00262450494035660,
 -0.00052657891389470,
  0.00011066592304919,
 -0.00002405432778364,
  5.36399803946e-6,
 -1.22044976064e-6,
  2.8222728362e-7,
 -6.614312964e-8,
  1.567608839e-8,
 -3.75084856e-9,
  9.0486546e-10,
 -2.1985553e-10,
  5.375401e-11,
 -1.321572e-11,
  3.26524e-12,
 -8.1033e-13,
  2.0190e-13,
 -5.049e-14,
  1.267e-14,
 -3.19e-15,
  8.0e-16,
 -2.0e-16
};

static const double * coef_jnu_a[] = {
  0,
  coef_jnu1_a,
  coef_jnu2_a,
  coef_jnu3_a,
  coef_jnu4_a,
  coef_jnu5_a,
  coef_jnu6_a,
  coef_jnu7_a,
  coef_jnu8_a,
  coef_jnu9_a,
  coef_jnu10_a,
  coef_jnu11_a,
  coef_jnu12_a,
  coef_jnu13_a,
  coef_jnu14_a,
  coef_jnu15_a,
  coef_jnu16_a,
  coef_jnu17_a,
  coef_jnu18_a,
  coef_jnu19_a,
  coef_jnu20_a
};

static const size_t size_jnu_a[] = {
  0,
  sizeof(coef_jnu1_a)/sizeof(double),
  sizeof(coef_jnu2_a)/sizeof(double),
  sizeof(coef_jnu3_a)/sizeof(double),
  sizeof(coef_jnu4_a)/sizeof(double),
  sizeof(coef_jnu5_a)/sizeof(double),
  sizeof(coef_jnu6_a)/sizeof(double),
  sizeof(coef_jnu7_a)/sizeof(double),
  sizeof(coef_jnu8_a)/sizeof(double),
  sizeof(coef_jnu9_a)/sizeof(double),
  sizeof(coef_jnu10_a)/sizeof(double),
  sizeof(coef_jnu11_a)/sizeof(double),
  sizeof(coef_jnu12_a)/sizeof(double),
  sizeof(coef_jnu13_a)/sizeof(double),
  sizeof(coef_jnu14_a)/sizeof(double),
  sizeof(coef_jnu15_a)/sizeof(double),
  sizeof(coef_jnu16_a)/sizeof(double),
  sizeof(coef_jnu17_a)/sizeof(double),
  sizeof(coef_jnu18_a)/sizeof(double),
  sizeof(coef_jnu19_a)/sizeof(double),
  sizeof(coef_jnu20_a)/sizeof(double)
};

static const double * coef_jnu_b[] = {
  0,
  coef_jnu1_b,
  coef_jnu2_b,
  coef_jnu3_b,
  coef_jnu4_b,
  coef_jnu5_b,
  coef_jnu6_b,
  coef_jnu7_b,
  coef_jnu8_b,
  coef_jnu9_b,
  coef_jnu10_b
};

static const size_t size_jnu_b[] = {
  0,
  sizeof(coef_jnu1_b)/sizeof(double),
  sizeof(coef_jnu2_b)/sizeof(double),
  sizeof(coef_jnu3_b)/sizeof(double),
  sizeof(coef_jnu4_b)/sizeof(double),
  sizeof(coef_jnu5_b)/sizeof(double),
  sizeof(coef_jnu6_b)/sizeof(double),
  sizeof(coef_jnu7_b)/sizeof(double),
  sizeof(coef_jnu8_b)/sizeof(double),
  sizeof(coef_jnu9_b)/sizeof(double),
  sizeof(coef_jnu10_b)/sizeof(double)
};

/* Evaluate Clenshaw recurrence for
 * a T* Chebyshev series.
 * sizeof(c) = N+1
 */
static double
clenshaw(const double * c, int N, double u)
{
  double B_np1 = 0.0;
  double B_n   = c[N];
  double B_nm1;
  int n;
  for(n=N; n>0; n--) {
    B_nm1 = 2.0*(2.0*u-1.0) * B_n - B_np1 + c[n-1];
    B_np1 = B_n;
    B_n   = B_nm1;
  }
  return B_n - (2.0*u-1.0)*B_np1;
}

/* correction terms to leading McMahon expansion
 * [Abramowitz+Stegun 9.5.12]
 * [Olver, Royal Society Math. Tables, v. 7]
 * We factor out a beta, so that this is a multiplicative
 * correction:
 *   j_{nu,s} = beta(s,nu) * mcmahon_correction(nu, beta(s,nu))
 *   macmahon_correction --> 1 as s --> Inf
 */
static double
mcmahon_correction(const double mu, const double beta)
{
  const double eb   = 8.0*beta;
  const double ebsq = eb*eb;

  if(mu < GSL_DBL_EPSILON) {
    /* Prevent division by zero below. */
    const double term1 =  1.0/ebsq;
    const double term2 = -4.0*31.0/(3*ebsq*ebsq);
    const double term3 =  32.0*3779.0/(15.0*ebsq*ebsq*ebsq);
    const double term4 = -64.0*6277237.0/(105.0*ebsq*ebsq*ebsq*ebsq);
    const double term5 =  512.0*2092163573.0/(315.0*ebsq*ebsq*ebsq*ebsq*ebsq);
    return 1.0 + 8.0*(term1 + term2 + term3 + term4 + term5);
  }
  else {
    /* Here we do things in terms of 1/mu, which
     * is purely to prevent overflow in the very
     * unlikely case that mu is really big.
     */
    const double mi   = 1.0/mu;
    const double r  = mu/ebsq;
    const double n2 = 4.0/3.0    * (7.0 - 31.0*mi);
    const double n3 = 32.0/15.0  * (83.0 + (-982.0 + 3779.0*mi)*mi);
    const double n4 = 64.0/105.0 * (6949.0 + (-153855.0 + (1585743.0 - 6277237.0*mi)*mi)*mi);
    const double n5 = 512.0/315.0 * (70197.0 + (-2479316.0 + (48010494.0 + (-512062548.0 + 2092163573.0*mi)*mi)*mi)*mi);
    const double n6 = 2048.0/3465.0 * (5592657.0 + (-287149133.0 + (8903961290.0 + (-179289628602.0 + (1982611456181.0 - 8249725736393.0*mi)*mi)*mi)*mi)*mi);
    const double term1 = (1.0 - mi) * r;
    const double term2 = term1 * n2 * r;
    const double term3 = term1 * n3 * r*r;
    const double term4 = term1 * n4 * r*r*r;
    const double term5 = term1 * n5 * r*r*r*r;
    const double term6 = term1 * n6 * r*r*r*r*r;
    return 1.0 - 8.0*(term1 + term2 + term3 + term4 + term5 + term6);
  }
}

/* Assumes z >= 1.0 */
static double
olver_b0(double z, double minus_zeta)
{
  if(z < 1.02) {
    const double a = 1.0-z;
    const double c0 =  0.0179988721413553309252458658183;
    const double c1 =  0.0111992982212877614645974276203;
    const double c2 =  0.0059404069786014304317781160605;
    const double c3 =  0.0028676724516390040844556450173;
    const double c4 =  0.0012339189052567271708525111185;
    const double c5 =  0.0004169250674535178764734660248;
    const double c6 =  0.0000330173385085949806952777365;
    const double c7 = -0.0001318076238578203009990106425;
    const double c8 = -0.0001906870370050847239813945647;
    return c0 + a*(c1 + a*(c2 + a*(c3 + a*(c4 + a*(c5 + a*(c6 + a*(c7 + a*c8)))))));
  }
  else {
    const double abs_zeta = minus_zeta;
    const double t = 1.0/(z*sqrt(1.0 - 1.0/(z*z)));
    return -5.0/(48.0*abs_zeta*abs_zeta) + t*(3.0 + 5.0*t*t)/(24.0*sqrt(abs_zeta));
  }
}

inline
static double
olver_f1(double z, double minus_zeta)
{
  const double b0 = olver_b0(z, minus_zeta);
  const double h2 = sqrt(4.0*minus_zeta/(z*z-1.0)); /* FIXME */
  return 0.5 * z * h2 * b0;
}

int
gsl_sf_bessel_zero_J0_e(unsigned int s, gsl_sf_result * result)
{
  /* CHECK_POINTER(result) */

  if(s == 0){
    result->val = 0.0;
    result->err = 0.0;
    GSL_ERROR ("error", GSL_EINVAL);
  }
  else {
    /* See [F. Lether, J. Comp. Appl .Math. 67, 167 (1996)]. */

    static const double P[] = { 1567450796.0/12539606369.0,
                                8903660.0/2365861.0,
                                10747040.0/536751.0,
                                17590991.0/1696654.0
                              };
    static const double Q[] = { 1.0,
                                29354255.0/954518.0,
                                76900001.0/431847.0,
                                67237052.0/442411.0
                              };

    const double beta = (s - 0.25) * M_PI;
    const double bi2  = 1.0/(beta*beta);
    const double R33num = P[0] + bi2 * (P[1] + bi2 * (P[2] + P[3] * bi2));
    const double R33den = Q[0] + bi2 * (Q[1] + bi2 * (Q[2] + Q[3] * bi2));
    const double R33 = R33num/R33den;
    result->val = beta + R33/beta;
    result->err = fabs(3.0e-15 * result->val);
    return GSL_SUCCESS;
  }
}

int
gsl_sf_bessel_zero_J1_e(unsigned int s, gsl_sf_result * result)
{
  /* CHECK_POINTER(result) */

  if(s == 0) {
    result->val = 0.0;
    result->err = 0.0;
    return GSL_SUCCESS;
  }
  else {
    /* See [M. Branders et al., J. Comp. Phys. 42, 403 (1981)]. */

    static const double a[] = { -0.362804405737084,
                                 0.120341279038597,
                                 0.439454547101171e-01,
                                 0.159340088474713e-02
                              };
    static const double b[] = {  1.0,
                                -0.325641790801361,
                                -0.117453445968927,
                                -0.424906902601794e-02
                              };

    const double beta = (s + 0.25) * M_PI;
    const double bi2  = 1.0/(beta*beta);
    const double Rnum = a[3] + bi2 * (a[2] + bi2 * (a[1] + bi2 * a[0]));
    const double Rden = b[3] + bi2 * (b[2] + bi2 * (b[1] + bi2 * b[0]));
    const double R = Rnum/Rden;
    result->val = beta * (1.0 + R*bi2);
    result->err = fabs(2.0e-14 * result->val);
    return GSL_SUCCESS;
  }
}

int
gsl_sf_bessel_zero_Jnu_e(double nu, unsigned int s, gsl_sf_result * result)
{
  /* CHECK_POINTER(result) */

  if(nu <= -1.0) {
    DOMAIN_ERROR(result);
  }
  else if(s == 0) {
    result->val = 0.0;
    result->err = 0.0;
    if (nu == 0.0) {
      GSL_ERROR ("no zero-th root for nu = 0.0", GSL_EINVAL);
    }
    return GSL_SUCCESS;
  }
  else if(nu < 0.0) {
    /* This can be done, I'm just lazy now. */
    result->val = 0.0;
    result->err = 0.0;
    GSL_ERROR("unimplemented", GSL_EUNIMPL);
  }
  else if(s == 1) {
    /* Chebyshev fits for the first positive zero.
     * For some reason Nemeth made this different from the others.
     */
    if(nu < 2.0) {
      const double * c = coef_jnu_a[s];
      const size_t   L = size_jnu_a[s];
      const double arg = nu/2.0;
      const double chb = clenshaw(c, L-1, arg);
      result->val = chb;
      result->err = 2.0e-15 * result->val;
    }
    else {
      const double * c = coef_jnu_b[s];
      const size_t   L = size_jnu_b[s];
      const double arg = pow(2.0/nu, 2.0/3.0);
      const double chb = clenshaw(c, L-1, arg);
      result->val = nu * chb;
      result->err = 2.0e-15 * result->val;
    }
    return GSL_SUCCESS;
  }
  else if(s <= 10) {
    /* Chebyshev fits for the first 10 positive zeros. */
    if(nu < s) {
      const double * c = coef_jnu_a[s];
      const size_t   L = size_jnu_a[s];
      const double arg = nu/s;
      const double chb = clenshaw(c, L-1, arg);
      result->val = chb;
      result->err = 2.0e-15 * result->val;
    }
    else {
      const double * c = coef_jnu_b[s];
      const size_t   L = size_jnu_b[s];
      const double arg = pow(s/nu, 2.0/3.0);
      const double chb = clenshaw(c, L-1, arg);
      result->val = nu * chb;
      result->err = 2.0e-15 * result->val;

      /* FIXME: truth in advertising for the screwed up
       * s = 5 fit. Need to fix that.
       */
      if(s == 5) {
        result->err *= 5.0e+06;
      }
    }
    return GSL_SUCCESS;
  }
  else if(s > 0.5*nu && s <= 20) {
    /* Chebyshev fits for 10 < s <= 20. */
    const double * c = coef_jnu_a[s];
    const size_t   L = size_jnu_a[s];
    const double arg = nu/(2.0*s);
    const double chb = clenshaw(c, L-1, arg);
    result->val = chb;
    result->err = 4.0e-15 * chb;
    return GSL_SUCCESS;
  }
  else if(s > 2.0 * nu) {
    /* McMahon expansion if s is large compared to nu. */
    const double beta = (s + 0.5*nu - 0.25) * M_PI;
    const double mc   = mcmahon_correction(4.0*nu*nu, beta);
    gsl_sf_result rat12;
    gsl_sf_pow_int_e(nu/beta, 14, &rat12);
    result->val  = beta * mc;
    result->err  = 4.0 * fabs(beta) * rat12.val;
    result->err += 4.0 * fabs(GSL_DBL_EPSILON * result->val);
    return GSL_SUCCESS;
  }
  else {
    /* Olver uniform asymptotic. */
    gsl_sf_result as;
    const int stat_as = gsl_sf_airy_zero_Ai_e(s, &as);
    const double minus_zeta = -pow(nu,-2.0/3.0) * as.val;
    const double z  = gsl_sf_bessel_Olver_zofmzeta(minus_zeta);
    const double f1 = olver_f1(z, minus_zeta);
    result->val  = nu * (z + f1/(nu*nu));
    result->err  = 0.001/(nu*nu*nu);
    result->err += 2.0 * GSL_DBL_EPSILON * fabs(result->val);
    return stat_as;
  }
}

/*-*-*-*-*-*-*-*-*-* Functions w/ Natural Prototypes *-*-*-*-*-*-*-*-*-*-*/

#include "eval.h"

double gsl_sf_bessel_zero_J0(unsigned int s)
{
  EVAL_RESULT(gsl_sf_bessel_zero_J0_e(s, &result));
}

double gsl_sf_bessel_zero_J1(unsigned int s)
{
  EVAL_RESULT(gsl_sf_bessel_zero_J1_e(s, &result));
}

double gsl_sf_bessel_zero_Jnu(double nu, unsigned int s)
{
  EVAL_RESULT(gsl_sf_bessel_zero_Jnu_e(nu, s, &result));
}


#include <stdlib.h>
#include <cblas.h>
#include <lapacke.h>

#include "../../runtime/pcp.h"
#include "tasks.h"
#include "utils.h"

#define BLOCK_SIZE 32
#define MAX_MATRIX_SIZE 1024
#define MAX_NUM_ITER (MAX_MATRIX_SIZE / BLOCK_SIZE)
#define MAX_NUM_THREADS 16

// The matrix partitioning for TRSM and SYRK in each iteration.
//
// A partitioning x of a range 0, ..., n - 1 over p threads is
// represented as an array x[] of length p + 1 such that x[k] is the
// start of the block assigned to thread k and x[k + 1] - x[k] is the
// size of the block assigned to thread k, for k = 0, ..., p - 1.
struct partition
{
    // The matrix size.
    int matrix_size;

    // The block size.
    int block_size;

    // The number of threads.
    int num_threads;

    // The number of iterations = ceil(matrix_size, block_size).
    int num_iter;

    // The partitionings for TRSM.
    int trsm[MAX_NUM_ITER][MAX_NUM_THREADS + 1];

    // The partitionings for SYRK.
    int syrk[MAX_NUM_ITER][MAX_NUM_THREADS + 1];
};

// Barrier used to synchronize the workers between iterations.
static pcp_barrier_t *barrier = NULL;

static struct partition *partition_db[MAX_MATRIX_SIZE + 1][MAX_NUM_THREADS + 1] = {NULL};
static struct partition *partition = NULL;

static double *A;
static int ldA;
static int n;
#define A(i,j) A[(i) + (j) * ldA]

// Initializes the partitioning for TRSM for a given iteration.
//
// Uses uniform row blocks.
static void part_init_trsm(int iter)
{
    const int last = partition->matrix_size;
    const int first = min(last, (iter + 1) * partition->block_size);
    const int size = last - first;
    const int p = partition->num_threads;
    const int chunk = iceil(size, p);
    partition->trsm[iter][0] = first;
    partition->trsm[iter][p] = last;
    for (int th = 1; th < p; ++th) {
        partition->trsm[iter][th] = min(last, partition->trsm[iter][th - 1] + chunk);
    }
}

// Initializes the partitioning for SYRK for a given iteration.
static void part_init_syrk(int iter)
{
    // Balance the load by flops.
    const int last = partition->matrix_size;
    const int first = min(last, (iter + 1) * partition->block_size);
    const int size = last - first;
    const int p = partition->num_threads;
    const int total_work = size * (size + 1) / 2;
    const int ideal_part_work = total_work / p;
    partition->syrk[iter][0] = first;
    partition->syrk[iter][p] = last;
    for (int k = 1; k < p; ++k) {
        partition->syrk[iter][k] = partition->syrk[iter][k - 1];
        int work = 0;
        while (work < ideal_part_work && partition->syrk[iter][k] < last) {
            partition->syrk[iter][k] += 1;
            work += partition->syrk[iter][k] - first;
        }
        if (k == 1) {
            partition->syrk[iter][k] = max(partition->syrk[iter][k], partition->syrk[iter][k - 1] + partition->block_size);
            partition->syrk[iter][k] = min(partition->syrk[iter][k], last);
        }
    }
}

// Returns the start of a TRSM block.
static int part_block_start_trsm(int iter, int me)
{
    return partition->trsm[iter][me];
}

// Returns the size of a TRSM block.
static int part_block_size_trsm(int iter, int me)
{
    return part_block_start_trsm(iter, me + 1) - part_block_start_trsm(iter, me);
}

// Returns the start of a SYRK block.
static int part_block_start_syrk(int iter, int me)
{
    return partition->syrk[iter][me];
}

// Returns the size of a SYRK block.
static int part_block_size_syrk(int iter, int me)
{
    return part_block_start_syrk(iter, me + 1) - part_block_start_syrk(iter, me);
}

// Initializes the partitioning for given parameters.
//
// Follows this protocol:
// 1) Re-use existing if it matches.
// 2) Load from file if it matches.
// 3) Create from scratch.
static void part_init(int matrix_size, int block_size, int num_threads)
{
    // Check if the partitioning is available in the databse.
    if (partition_db[matrix_size][num_threads] == NULL) {
        // No, create from scratch and save in the database.
        partition = malloc(sizeof(*partition));
        partition->matrix_size = matrix_size;
        partition->block_size = block_size;
        partition->num_threads = num_threads;
        partition->num_iter = iceil(matrix_size, block_size);
        for (int iter = 0; iter < partition->num_iter; ++iter) {
            part_init_trsm(iter);
            part_init_syrk(iter);
        }
        partition_db[matrix_size][num_threads] = partition;
    } else {
        // Yes, reuse from database.
        partition = partition_db[matrix_size][num_threads];
    }
}

void chol_task_par_reconfigure(int nth)
{
    // Choose current size of the worker pool for barrier.
    if (barrier != NULL) {
        pcp_barrier_destroy(barrier);
    }
    barrier = pcp_barrier_create(nth);
}

void chol_task_par_finalize(void)
{
    if (barrier != NULL) {
        pcp_barrier_destroy(barrier);
        barrier = NULL;
    }
}

static void krnl_chol(int iter)
{
    int jp      = iter * partition->block_size;
    int n       = min(partition->matrix_size - jp, partition->block_size);
    double *A11 = &A(jp,jp);

    LAPACKE_dpotrf(LAPACK_COL_MAJOR, 'L', n, A11, ldA);
}

static void krnl_syrk(int iter, int me)
{
    //   j1        j2
    // i g g g g g s
    //   g g g g g s s
    //   g g g g g s s s
    //   g g g g g s s s s
    //
    // g = gemm
    // s = syrk

    int jp = iter * partition->block_size;
    int i  = part_block_start_syrk(iter, me);
    int m  = part_block_size_syrk(iter, me);
    int j1 = jp + partition->block_size;
    int j2 = i;
    int n1 = j2 - j1;
    int k  = partition->block_size;

    double *A21;
    double *A21a, *A21b;
    double *A22;

    /* SYRK */
    if (m > 0) {
        A22 = &A(i,j2);
        A21 = &A(i,jp);
        cblas_dsyrk(CblasColMajor, CblasLower, CblasNoTrans,
                    m, k,
                    -1.0, A21, ldA,
                     1.0, A22, ldA);
    }

    /* GEMM */
    if (m > 0 && n1 > 0) {
        A22  = &A(i,j1);
        A21a = &A(i,jp);
        A21b = &A(j1,jp);
        cblas_dgemm(CblasColMajor, CblasNoTrans, CblasTrans,
                    m, n1, k,
                    -1.0, A21a, ldA,
                          A21b, ldA,
                     1.0, A22,  ldA);
    }
}

static void krnl_trsm(int iter, int me)
{
    int jp      = iter * partition->block_size;
    int i       = part_block_start_trsm(iter, me);
    int m       = part_block_size_trsm(iter, me);
    int n       = min(partition->matrix_size - jp, partition->block_size);
    double *A11 = &A(jp,jp);
    double *A21 = &A(i,jp);

    if (m > 0) {
        cblas_dtrsm(CblasColMajor, CblasRight, CblasLower, CblasTrans, CblasNonUnit,
                    m, n,
                    1.0, A11, ldA,
                         A21, ldA);
    }
}

void chol_task_par(void *ptr, int nth, int me)
{
    struct chol_task_arg *arg = (struct chol_task_arg*) ptr;

    n   = arg->n;
    A   = arg->A;
    ldA = arg->ldA;

    if (me == 0) {
        part_init(n, BLOCK_SIZE, nth);
    }

    if (me == 0) {
        // chol(0)
        krnl_chol(0);
    }

    // Synchronize.
    pcp_barrier_wait(barrier);

    // Loop over iterations.
    for (int iter = 0; iter < partition->num_iter; ++iter) {
        const int final_iteration = (iter == partition->num_iter - 1);

        // Synchronize
        pcp_barrier_wait(barrier);

        //
        // Phase I: trsm(iter) in //
        //

        krnl_trsm(iter, me);

        // Synchronize
        pcp_barrier_wait(barrier);

        //
        // Phase II: syrk(iter) in // plus chol(iter + 1)
        //

        krnl_syrk(iter, me);
        if (me == 0 && !final_iteration) {
            krnl_chol(iter + 1);
        }
    }

    // Synchronize
    pcp_barrier_wait(barrier);
}


#if !defined (STATISTICS_H)
#define STATISTICS_H

#include <iostream>
#include <fstream>
#include <string>
#include <cmath>
#include <vector>
#include <algorithm>
#include "Rand.h"
#include <gsl/gsl_sf_gamma.h>
#include <gsl/gsl_sf_bessel.h>

using namespace std;

const int         XMAX = 600;
const long double EXPXMAX = exp((double)XMAX);
const long double EXP     = exp(1.0);

double Beta(double t, double x, double Itot);
double Gaussian(double x, double mean, double var);
long double Poisson(int lambda, int k);

void Statistics(const vector<double>& data, double& ave, double& squareave, double& var);

void Statistics(const vector<double>& data, double& max, double& min, double& ave, double& var, double& error);
void Statistics(const vector<int>& data, double& max, double& min, double& ave, double& var, double& error);
void Statistics(const vector<int>& data, double& xmax, double& xmin, double& xave, double& xvar, double& xerror, double& x2ave);

//void Statistics(const vector<int>& data, double& xmax, double& xmin, double& xave, double& xvar, double& xerror, double& x2ave, double& x1inverseave);

void Statistics(const vector<int>& data, double& Xmin, double& Xmax,
		double& X1inverseave, //  <1/(X+1)>
		double& logXave,      //  < logX >
		double& Xsrave,       //  < X^0.5 >
		double& Xave,         //  < X >
		double& X2ave );      //  < x^2 >

void moment(const vector<int>& data, double &ave, double &adev, double &sdev, double &var, double &skew, double &curt);

void StatisticalDispersion(vector<int>& X, double& H7, double& H8, double& H9, double& H10);

double GetXsecondmin(const vector<double>& data, double Xmin);

void Statistics(const vector<double>& data,
		double& min, double& Q1, double& Q2, double& Q3, double& max,
		double& ave, double& var, double& error,
		ofstream& fout1, ofstream& fout2);

void Binning(const vector<double>& data, int B, vector<double>& Binsize, vector<double>& Q);
int GetBinIndex(const double& x, const vector<double>& Q);

#endif


#include "common.h"
#include "dMatrix.h"
#include "options.h"
#include "linefile.h"
#include <gsl/gsl_math.h>
#include <gsl/gsl_statistics_double.h>

struct rnaBinder
/* Name and correlation of rna binding proteins. */
{
    struct rnaBinder *next; /* Next in list. */
    char *psName;           /* Name of probe set. Something like
		 	     * E@NM_XXXXXX_a_at or GXXXXXXX_a_at.*/
    char *geneName;         /* Name of gene probed by probe
			     * set. Hopefully something readable. */
    char *pfamAcc;          /* Name of pfam accession. */
    char *pfamName;         /* Name of pfam domain. */
    double corr;           /* Correlation between rnaBinder and cluster of interest. */
};

struct clusterMember
/* Member of the cluster of interest. */
{
    struct clusterMember *next; /* Next in list. */
    char *geneId;               /* Gene id in cluster. */
    char *psName;               /* Name of the probe set. */
    char *desc;                 /* Description of the probe set. */
};

static struct optionSpec optionSpecs[] =
/* Our acceptable options to be called with. */
{
    {"help", OPTION_BOOLEAN},
    {"sjIndexFile", OPTION_STRING},
    {"psFile", OPTION_STRING},
    {"clusterFile", OPTION_STRING},
    {"rnaBindingFile", OPTION_STRING},
    {"outputFile", OPTION_STRING},
    {"antiCorrelation", OPTION_BOOLEAN},
    {NULL, 0}
};

static char *optionDescripts[] =
/* Description of our options for usage summary. */
{
    "Display this messge.",
    "File with splice junction indexes.",
    "File with probe set intensities.",
    "File with probe sets in cluster of interest.",
    "File with rna binding gene sets.",
    "File to output correlation results to.",
    "Sort by anti-correlation rather than correlation."
};

void usage()
/* Print usage and quit. */
{
int i=0;
warn("rnaBindingClusterCorr - Program to calculate the correlation\n"
     "between rna binding protein expression data and splice junctions\n"
     "co-regulated as determined by clustering splice junction indexes.\n"
     "options are:");
for(i=0; i<ArraySize(optionSpecs) -1; i++)
    fprintf(stderr, "  -%s -- %s\n", optionSpecs[i].name, optionDescripts[i]);
errAbort("\nusage:\n"
	 "   rnaBindingClusterCorr -sjIndexFile=input/sjIndexTest.tab -psFile=input/psIntenTest.tab \\ \n"
         "      -clusterFile=input/clusterTest.tab -rnaBindingFile=input/rnaBinderTest.tab \\ \n"
         "      -outputFile=output/test.out.tab\n");
}

double correlation(double *X, double *Y, int count)
/* Compute the correlation between X and Y
   correlation(X,Y) = cov(X,Y)/ squareRt(var(X)var(Y))
   page 332 Sheldon Ross "A First Course in Probability" 1998
*/
{
double varX = gsl_stats_variance(X, 1, count);
double varY = gsl_stats_variance(Y, 1, count);
double covXY = gsl_stats_covariance(X, 1, Y, 1, count);

double correlation = covXY / sqrt(varX *varY);
return correlation;
}

double calcDistanceFromCluster(struct rnaBinder *rb, struct clusterMember *cmList,
			       struct dMatrix *sjIndex, struct dMatrix *psInten)
/* Calculate the distance from the rnaBinder intensity measurement to
   the sjIndexes of the cluster members. If no intensity present use
   0 as it will fall in the middle of [-1,1]. */
{
double sum = 0;
int count = 0;
int sjIx = 0, gsIx = 0;
struct clusterMember *cm = NULL;
double corr = 0;
if(sjIndex->colCount != psInten->colCount)
    errAbort("Splice Junction and Intensity files must have same number of columns.");

/* Get the index of the gene set in the intensity file. */
gsIx = hashIntValDefault(psInten->nameIndex, rb->psName, -1);
if(gsIx == -1)
    {
/*     warn("Probe Set %s not found in intensitiy file."); */
    return 0;
    }
for(cm = cmList; cm != NULL; cm = cm->next)
    {
    /* For each member get the index in the splice junction file. */
    sjIx = hashIntValDefault(sjIndex->nameIndex, cm->psName, -1);
    if(sjIx == -1)
	errAbort("Probe Set %s not found in SJ index file.");
    corr = correlation(psInten->matrix[gsIx], sjIndex->matrix[sjIx], sjIndex->colCount);
    sum += corr;
    count++;
    }
if(count == 0)
    errAbort("No junctions in cluster.");
sum = sum / (double) count;
return sum;
}

int rnaBinderCmp(const void *va, const void *vb)
/* Compare to sort based correlation. */
{
const struct rnaBinder *a = *((struct rnaBinder **)va);
const struct rnaBinder *b = *((struct rnaBinder **)vb);
return a->corr < b->corr;
}

struct rnaBinder *loadRnaBinders()
/* Load the probe sets that encode genes thought to
   bind rnas. Expected order is probeSet, geneName, pfamAcc, pfamName */
{
struct rnaBinder *rbList = NULL, *rb = NULL;
char *words[4];
struct lineFile *lf = NULL;
char *inputFile = optionVal("rnaBindingFile", NULL);

assert(inputFile);
lf = lineFileOpen(inputFile, TRUE);
while(lineFileChopCharNext(lf, '\t', words, ArraySize(words)))
    {
    AllocVar(rb);
    rb->psName = cloneString(words[0]);
    rb->geneName = cloneString(words[1]);
    rb->pfamAcc = cloneString(words[2]);
    rb->pfamName = cloneString(words[3]);
    slAddHead(&rbList, rb);
    }
lineFileClose(&lf);
slReverse(&rbList);
return rbList;
}

struct clusterMember *loadClusterMembers()
/* Load the probe sets that are in our cluster of interest. */
{
struct clusterMember *cmList = NULL, *cm = NULL;
char *words[3];
struct lineFile *lf = NULL;
char *inputFile = optionVal("clusterFile", NULL);
int wordCount = 0;
assert(inputFile);
lf = lineFileOpen(inputFile, TRUE);
while((wordCount = lineFileChopCharNext(lf, '\t', words, ArraySize(words))) != 0)
    {
    AllocVar(cm);
    if(wordCount == 3)
	{
	cm->geneId = cloneString(words[0]);
	cm->psName = cloneString(words[1]);
	cm->desc = cloneString(words[2]);
	}
    else if(wordCount == 2)
	{
	cm->psName = cloneString(words[0]);
	cm->desc = cloneString(words[1]);
	}
    else
	errAbort("Got %d words at line %d", wordCount, lf->lineIx);
    slAddHead(&cmList, cm);
    }
lineFileClose(&lf);
slReverse(&cmList);
return cmList;
}

void reportRnaBinders(struct rnaBinder *rbList)
/* Report the rnaBinder distances. */
{
struct rnaBinder *rb = NULL;
char *fileName = optionVal("outputFile", NULL);
FILE *out = NULL;

if(fileName == NULL)
    errAbort("Must specify an outputFile");
out = mustOpen(fileName, "w");
for(rb = rbList; rb != NULL; rb = rb->next)
    {
    fprintf(out, "%s\t%s\t%s\t%s\t%.4f\n",
	    rb->psName, rb->geneName, rb->pfamAcc, rb->pfamName, rb->corr);
    }
carefulClose(&out);
}

void rnaBindingClusterCorr()
/* Top level function to calculate correlations. */
{
char *sjIndexFile = optionVal("sjIndexFile", NULL);
char *psFile = optionVal("psFile", NULL);
struct dMatrix *sjIndex = NULL, *psInten = NULL;
struct rnaBinder *rbList = NULL, *rb = NULL;
struct clusterMember *cmList = NULL, *cm = NULL;

warn("Loading Files.");
sjIndex = dMatrixLoad(sjIndexFile);
psInten = dMatrixLoad(psFile);
rbList = loadRnaBinders();
cmList = loadClusterMembers();

warn("Calculating Distance.");
/* Calculate distance. */
for(rb = rbList; rb != NULL; rb = rb->next)
    rb->corr = calcDistanceFromCluster(rb, cmList, sjIndex, psInten);

/* Sort by distance (correlati2n) */
slSort(&rbList, rnaBinderCmp);

/* If we want anti-correlation reverse. */
if(optionExists("antiCorrelation"))
    slReverse(&rbList);

warn("Writing out results.");
/* Do some outputting. */
reportRnaBinders(rbList);

/* Cleanup. */
dMatrixFree(&sjIndex);
dMatrixFree(&psInten);
warn("Done.");
}

int main(int argc, char *argv[])
{
if(argc == 1)
    usage();
optionInit(&argc, argv, optionSpecs);
if(optionExists("help"))
    usage();
rnaBindingClusterCorr();
return 0;
}


#pragma once

#include <string>

#include <gsl/gsl_complex.h>

#include "Expression.h"
#include "UnitConversionExpression/Units.h"

class BaseUnitExpression: public Expression {

    UnitType type;
    std::string abbr;
    double val;

    BaseUnitExpression(const std::string& unit, double val);
    BaseUnitExpression(UnitType type, const std::string& unit, double val);

    public:
        static expression construct(const std::string& unit, double val = 1);
        static expression construct(UnitType type, const std::string& unit, double val);

        std::string repr() const override;
        int id() const override;

        EXPRESSION_OVERRIDES

        friend expression operator*(const BaseUnitExpression& unit1, const BaseUnitExpression& unit2);
        friend expression operator*(const BaseUnitExpression& unit1, const expression expr);
        friend expression operator/(const BaseUnitExpression& unit1, const BaseUnitExpression& unit2);
        friend expression operator/(const BaseUnitExpression& unit1, const expression expr);
        friend expression operator^(const BaseUnitExpression& unit1, const expression expr);

        friend expression convert(const BaseUnitExpression& from, const BaseUnitExpression& to);
};

class ConvertedUnitExpression: public Expression {

    UnitType type;
    std::string abbr;
    double val;

    ConvertedUnitExpression(UnitType type, const std::string& unit, double val);

    public:
        static expression construct(UnitType type, const std::string& unit, double val);

        std::string repr() const override;
        int id() const override;

        EXPRESSION_OVERRIDES
};


#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_linalg.h>
#include <gsl/gsl_blas.h>
#include "Watershed.h"
#include "math_functions.h"
#include "Globals.h"
#include "constitutive_equations.h"
#include "dgshed_numerical_flux.h"
#include "manufactured_solution.h"

/*****************************************************************************************//**
 * @file computeL.c
 *
 * This file contains code to evaluate the right hand side of the following discrete ODE obtained
 * from the DG discretization of the 2-D shallow water equations:
 *
 * \f$\frac{\partial \hat{\mathbf{w}}}{\partial t} = M^{-1}L(\hat{\mathbf{w}},t)\f$
 *
 * *******************************************************************************************/

/***************************************************************************/
/************ Compute the spatial discretization of the equation ***********/
/***************************************************************************/
extern void GetLGLWeights(int N, double *w);
extern double receive_floodwater(struct TwoDRegion *floodplain, int edg);

void computeChanL(struct channel* Chan, double time, double *dt, int channelNumber, int stage, double* RHSA, double* RHSQ)
{
	int fluxType = 1;
	int NumEdges = Chan->NumEdges;
	int NumEl = Chan->NumEl;
	int NumNodes = Chan->NumNodes;
	int Np = Chan->Np;
	int P = Chan->P;

#ifdef WDON
	/**** Compute the mass over an element to use later for ensuring ********/
	/**** positive mass with the flux in a wetting and drying treatment *************/
	double* mass = xcalloc(NumEl, sizeof(double));
	double LGLWeight[Np];
	GetLGLWeights(P, LGLWeight);

	for (int i = 0; i < NumEl; ++i)
	{
		double avgArea = 0;
		int begNode = i*Np + 1;
		for (int j =0; j < Np; j++)
		{
			avgArea += LGLWeight[j]*Chan->A[begNode+j];
		}
		mass[i] = avgArea;
	}
#endif

	/************** Compute the numerical flux at the faces *****************/
	double* Fhat1L = xcalloc(NumEdges, sizeof(double));
	double* Fhat2L = xcalloc(NumEdges, sizeof(double));
	double* Fhat1R = xcalloc(NumEdges, sizeof(double));
	double* Fhat2R = xcalloc(NumEdges, sizeof(double));

	for (int i=0; i < NumEdges; ++i)
	{
		double A_L, Q_L, A_R, Q_R, m1val, m2val;

		int leftNode = i*Np;
		int rightNode = leftNode+1;

		m1val = Chan->m1[i];
		m2val = Chan->m2[i];
		double bval = Chan->b[i];

		double tmpF[2];

#ifdef WDON
		// Check to see if the elements separated by this boundary are both dry
		if (i > 0 && i < NumEdges-1)
		{
			if (Chan->WD[i-1] == 0 && Chan->WD[i] == 0)
			{
				// Reflection flux for the left element
				A_L = Chan->A[leftNode];
				A_R = A_L;
				Q_L = Chan->Q[leftNode];
				Q_R = -Q_L;

				if (fluxType == 1)
					RoeFluxChan(tmpF, A_L, A_R, Q_L, Q_R,bval, m1val, m2val,0);
				else if (fluxType == 2)
					LFChan(tmpF, A_L, A_R, Q_L, Q_R,bval, m1val, m2val, 0);

				else
				{
					printf("Unknown flux type. Exiting now \n");
					exit(1);
				}

				Fhat1L[i] = tmpF[0];
				Fhat2L[i] = tmpF[1];

				// Reflection flux for the right element
				A_R = Chan->A[rightNode];
				A_L = A_R;
				Q_R = Chan->Q[rightNode];
				Q_L = -Q_R;

				if (fluxType == 1)
					RoeFluxChan(tmpF, A_L, A_R, Q_L, Q_R, bval, m1val, m2val, 0);
				else if (fluxType == 2)
					LFChan(tmpF, A_L, A_R, Q_L, Q_R, bval, m1val, m2val, 0);
				else
				{
					printf("Unknown flux type. Exiting now \n");
					exit(1);
				}
				Fhat1R[i] = tmpF[0];
				Fhat2R[i] = tmpF[1];

				if (isnan(tmpF[0]) || isnan(tmpF[1]))
				{
					printf("both elements dry flux not a number, edge %d \n",i);
					exit(EXIT_FAILURE);
				}

			}

		}

		// if the elements are not both dry
		if ((i == 0) || (i == NumEl) || Chan->WD[i-1] == 1 || Chan->WD[i] == 1)
		{
			A_L = Chan->A[leftNode];
			A_R = Chan->A[rightNode];

			Q_L = Chan->Q[leftNode];
			Q_R = Chan->Q[rightNode];

			if (fluxType == 1)
				RoeFluxChan(tmpF, A_L, A_R, Q_L,Q_R,bval, m1val, m2val, g);
			else if (fluxType == 2)
				LFChan(tmpF, A_L, A_R, Q_L,Q_R,bval, m1val, m2val, g);

			else
			{
				printf("Unknown flux type. Exiting now \n");
				exit(1);
			}

			if (isnan(tmpF[0]) || isnan(tmpF[1]))
			{
				printf("A_L = %lf A_R = %lf Q_L = %lf Q_R = %lf\n", A_L, A_R, Q_L, Q_R);
				printf(" both elements wet flux not a number, edge %d, channel %d , time = %lf\n",i, channelNumber, time);
				exit(EXIT_FAILURE);
			}

			if (i==0 || Chan->WD[i-1] == 1)
			{
				Fhat1L[i] = tmpF[0];
				Fhat2L[i] = tmpF[1];

			}
			else
			{
				double newtmpF[2];
				if (fluxType == 1)
					RoeFluxChan(newtmpF, A_L, A_R, Q_L, Q_R, bval, m1val, m2val, 0);
				else if (fluxType == 2)
					LFChan(newtmpF, A_L, A_R, Q_L, Q_R, bval, m1val, m2val, 0);
				else
				{
					printf("Unknown flux type. Exiting now \n");
					exit(1);
				}
				Fhat1L[i] = newtmpF[0];
				Fhat2L[i] = newtmpF[1];
				if (isnan(newtmpF[0]) || isnan(newtmpF[1]))
				{
					printf("left element dry flux not a number, edge %d \n",i);
					exit(EXIT_FAILURE);
				}

			}
			if (i == NumEl || Chan->WD[i]==1)
			{
				Fhat1R[i] = tmpF[0];
				Fhat2R[i] = tmpF[1];
			}
			else
			{
				if (fluxType == 1)
					RoeFluxChan(tmpF, A_L,A_R,Q_L,Q_R,bval, m1val, m2val, 0);
				else if (fluxType == 2)
					LFChan(tmpF, A_L,A_R,Q_L,Q_R,bval, m1val, m2val, 0);
				else
				{
					printf("Unknown flux type. Exiting now \n");
					exit(1);
				}
				Fhat1R[i] = tmpF[0];
				Fhat2R[i] = tmpF[1];
				if (isnan(tmpF[0]) || isnan(tmpF[1]))
				{
					printf("right element dry flux not a number, edge %d \n",i);
					printf("A_L = %lf A_R = %lf Q_L = %lf Q_R = %lf bval = %lf m1val = %lf m2val = %lf\n", A_L, A_R, Q_L, Q_R, bval, m1val, m2val);
					printf("F = %lf\n", tmpF[0]);
					exit(EXIT_FAILURE);
				}

			}
		}

#else
		A_L = Chan->A[leftNode];
		A_R = Chan->A[rightNode];

		Q_L = Chan->Q[leftNode];
		Q_R = Chan->Q[rightNode];

		if (fluxType == 1)
			RoeFluxChan(tmpF, A_L, A_R, Q_L,Q_R,bval,m1val,m2val,g);
		else if (fluxType == 2)
			LFChan(tmpF, A_L, A_R, Q_L,Q_R,bval,m1val,m2val,g);
		else
		{
			printf("Unknown flux type. Exiting now \n");
			exit(1);
		}

		if (isnan(tmpF[0]) || isnan(tmpF[1]))
		{
			printf("flux not a number, edge %d \n",i);

			printf("A_L = %3.16f A_R = %3.16f Q_L = %3.16f Q_R = %31.6f, b = %lf, m1 = %lf, m2 = %lf \n", A_L, A_R, Q_L, Q_R, Chan->b[i], m1val, m2val);

			printf("Fhat = %lf Ghat = %lf\n", tmpF[0], tmpF[1]);
			exit(EXIT_FAILURE);
		}

		Fhat1L[i] = tmpF[0];
		Fhat2L[i] = tmpF[1];

		Fhat1R[i] = tmpF[0];
		Fhat2R[i] = tmpF[1];

#endif

		/**************************************************************************************************/
		double u_L = Q_L/A_L;
		double u_R = Q_R/A_R;
		//approximate c_L (exact for rectangular channels, but not for trapezoidal
		double c_L = sqrt(g*A_L/Chan->b[i]);
		double c_R = sqrt(g*A_R/Chan->b[i]);

		if (i==0)
			Chan->max_lambda = fmax((fabs(u_L)+c_L), (fabs(u_R)+c_R));
		// Compute maximum eigenvalue for the next time step
		double current_max =fmax((fabs(u_L) + c_L), (fabs(u_R) + c_R));
		Chan->max_lambda = max(Chan->max_lambda,current_max);
	}

#ifdef WDON
	for (int i =1; i < NumEdges-1; ++i)
	{
		int leftNode = i*Np;
		int rightNode = leftNode+1;
		double maxBetaOverAlpha = 2;
		// Check to see if this flux might possibly result in negative mass
		// If the mass will be negative on the left side
		//int cont = 1;
		//while(cont)
		//{
			if (Fhat1L[i]*maxBetaOverAlpha*(*dt) > mass[i-1] || -Fhat1R[i]*maxBetaOverAlpha*(*dt) > mass[i] )
			{
				Fhat1L[i] = 0.0;
				Fhat2L[i] = 0.0;
				Fhat1R[i] = 0.0;
				Fhat2R[i] = 0.0;

				//double A_L = Chan->A[leftNode];
				//double A_R = A_L;
				//double Q_L = Chan->Q[leftNode];
				//double Q_R = -Q_L;
				//double bval = Chan->NodalB[leftNode];
				//double m1val = Chan->Nodalm1[leftNode];
				//double m2val = Chan->Nodalm2[leftNode];

				//double tmpF[2];
				//double localG = g;

				//if (Chan->WD[i] == 0)
				//	localG = 0;

				//if (fluxType == 1)
				//	RoeFluxChan(tmpF, A_L, A_R, Q_L, Q_R, bval,m1val,m2val,localG);
				//else if (fluxType == 2)
				//	LFChan(tmpF, A_L, A_R, Q_L, Q_R, bval,m1val,m2val,localG);
				//else
				//{
				//	printf("Unknown flux type. Exiting now \n");
				//	exit(1);
				//}
				//Fhat1L[i] = tmpF[0];
				//Fhat2L[i] = tmpF[1];
				//printf("element %d of Channel %d will be dry. Reducing dt now.\n", i, channelNumber);
				//(*dt) = 0.5*(*dt);
			}
			//else
			//	cont = 0;
		//}

		//cont = 1;
		//while(cont)
		//{
		//	// If the mass will be negative on the right side
		//	if (-Fhat1R[i]*maxBetaOverAlpha*(*dt) > mass[i])
		//	{
		//		double A_R = Chan->A[rightNode];
		//		double A_L = A_R;
		//		double Q_R = Chan->Q[rightNode];
		//		double Q_L = -Q_R;
		//		double bval = Chan->NodalB[rightNode];
		//		double m1val = Chan->Nodalm1[rightNode];
		//		double m2val = Chan->Nodalm2[rightNode];

		//		double tmpF[2];
		//		double localG = g;
		//		if (Chan->WD[i] == 0)
		//			localG = 0;

		//		if (fluxType == 1)
		//			RoeFluxChan(tmpF, A_L, A_R, Q_L, Q_R, bval, m1val, m2val,localG);
		//		else if (fluxType == 2)
		//			LFChan(tmpF, A_L, A_R, Q_L, Q_R, bval,m1val,m2val,localG);
		//		else
		//		{
		//			printf("Unknown flux type. Exiting now \n");
		//			exit(1);
		//		}

		//		Fhat1R[i] = tmpF[0];
		//		Fhat2R[i] = tmpF[1];
		//		printf("element %d of channel %d will be dry. Decreasing dt now.\n",i, channelNumber);

		//		*dt = 0.5*(*dt);

		//	}
		//	else
		//		cont = 0;
		//}
	}
	free(mass);
#endif

	for (int k=0; k < NumEl; ++k)
	{

		double h = Chan->dh[k];
		gsl_vector *F1 = gsl_vector_alloc(Np);
		gsl_vector *F2 = gsl_vector_alloc(Np);
		gsl_vector *ST21 = gsl_vector_alloc(Np);
		gsl_vector *ST22 = gsl_vector_alloc(Np);
		gsl_vector *ST23 = gsl_vector_alloc(Np);

		//ST11 contains the qL term that comes from overland flow
		gsl_vector *ST11 = gsl_vector_calloc(Np);

		// ST12 contains term that takes flooding into account
		gsl_vector *ST12 = gsl_vector_calloc(Np);

		int begNode = k*Np;
		for (int i = 0; i < Np; i++)
		{
			double Aval = Chan->A[begNode+i+1];
			double Qval = Chan->Q[begNode+i+1];
			double qL = Chan->qL[begNode+i];
			double qM = Chan->qM[begNode+i];
			double bval = Chan->NodalB[begNode+i];
			double S0 = Chan->dz[begNode+i];
			double m1val = Chan->Nodalm1[begNode+i];
			double m2val = Chan->Nodalm2[begNode+i];
			double dm1val = Chan->dm1[begNode+i];
			double dm2val = Chan->dm2[begNode+i];
			double dbval = Chan->db[begNode+i];
			double nval = Chan->NodalnFriction[begNode+i];
			double I1val = getI1(Aval, bval, m1val, m2val);
			double I2val = getI2(Aval, bval, dbval, m1val, dm1val, m2val, dm2val);
			double Sfval = getS_f(Aval, Qval, bval, m1val, m2val, nval);
			double beta = Chan->beta[begNode+i];
			double localG;
#ifdef WDON
			if(Chan->WD[k]==1)
				localG = g;
			else
				localG = 0;
#else
			localG = g;
#endif

			gsl_vector_set(F1, i, Qval);
			gsl_vector_set(F2, i, beta*Qval*Qval/Aval + localG*I1val);
			gsl_vector_set(ST21, i, localG*I2val);
			gsl_vector_set(ST22, i, localG*Aval*S0);
			gsl_vector_set(ST23, i, localG*Aval*Sfval);
			gsl_vector_set(ST11, i, qL);
			gsl_vector_set(ST12, i, qM);

		}

		//gsl_blas_dgemv(CblasNoTrans, 2.0/h, InvM, ST, 1.0, ST11);

		gsl_vector *localFhat1 = gsl_vector_alloc(2);
		gsl_vector_set(localFhat1, 0, Fhat1R[k]);
		gsl_vector_set(localFhat1, 1, Fhat1L[k+1]);

		gsl_vector *localFhat2 = gsl_vector_alloc(2);
		gsl_vector_set(localFhat2, 0, Fhat2R[k]);
		gsl_vector_set(localFhat2, 1, Fhat2L[k+1]);

		// cacluate the volume integral of the flux
		gsl_vector *localRHS1 = gsl_vector_calloc(Np);
		gsl_vector *localRHS2 = gsl_vector_calloc(Np);
		gsl_blas_dgemv(CblasNoTrans, 2.0/h, VolMat, F1, 1.0, localRHS1);
		gsl_blas_dgemv(CblasNoTrans, 2.0/h, VolMat, F2, 1.0, localRHS2);

		// calculate the surface integral of the flux
		gsl_vector *SurfPart1 = gsl_vector_calloc(Np);
		gsl_vector *SurfPart2 = gsl_vector_calloc(Np);
		gsl_blas_dgemv(CblasNoTrans, 2.0/h, LIFT, localFhat1, 1.0, SurfPart1);
		gsl_blas_dgemv(CblasNoTrans, 2.0/h, LIFT, localFhat2, 1.0, SurfPart2);

		// calculate the RHS
		gsl_vector_add(localRHS1, SurfPart1);
		gsl_vector_add(localRHS1, ST11);
		gsl_vector_add(localRHS1, ST12);
		gsl_vector_add(localRHS2, SurfPart2);
		gsl_vector_add(localRHS2, ST21);
		gsl_vector_add(localRHS2, ST22);
		gsl_vector_sub(localRHS2, ST23);

		for(int i = 0; i < Np; i++)
		{
			RHSA[begNode+i+1] = gsl_vector_get(localRHS1, i);
			RHSQ[begNode+i+1] = gsl_vector_get(localRHS2, i);
		}

		gsl_vector_free(F1);
		gsl_vector_free(F2);
		gsl_vector_free(localFhat1);
		gsl_vector_free(localFhat2);
		gsl_vector_free(SurfPart1);
		gsl_vector_free(SurfPart2);
		gsl_vector_free(ST11);
		gsl_vector_free(ST12);
		gsl_vector_free(ST21);
		gsl_vector_free(ST22);
		gsl_vector_free(ST23);
		gsl_vector_free(localRHS1);
		gsl_vector_free(localRHS2);

	}

	free(Fhat1L);
	free(Fhat1R);
	free(Fhat2L);
	free(Fhat2R);

}

void computeLKinematicEls(double time, double* RHS, int fp)
{
	int NumEl = FloodplainList[fp]->NumEl;
	// calculate and store the flux (Q) at the downstream edge of each kinematic element
	// these fluxes will be used to calculate the upwinded flux values
	double *Fhat = xcalloc(NumEl, sizeof(double));
	for (int i = 0; i < NumEl; ++i)
	{
		struct kinematicEl *kinEl = KinematicElList[i];
		if (kinEl->isActive)
		{
			int Np = kinEl->Np;
			double myA = kinEl->A[Np-1];
			double myS0 = kinEl->dz[Np-1];
			double myNf = kinEl->NodalnFriction[Np-1];
			double myWeq = kinEl->weq;
			double myH = myA/myWeq;

			// using manning's N.
			Fhat[i] = myWeq*sqrt(myS0)*pow(myH, 5.0/3)/myNf;

			// using chezy's relationship. nf stands for C, chezy's coefficient
			//Fhat[i] = myWeq*sqrt(myS0*myH)*myH*myNf;
		}
	}

	// now calculate the right hand side
	for (int i = 0; i < NumEl; ++i)
	{
		struct kinematicEl *kinEl = KinematicElList[i];
		if (kinEl->isActive)
		{
			double h_el = kinEl->dh;
			int Np = kinEl->Np;

			gsl_vector *F = gsl_vector_calloc(Np);

			gsl_vector *ST = gsl_vector_calloc(Np);			 // will eventually store R-I (Rainfall - Infiltration)

			double weq = kinEl->weq;

			for (int j = 0; j < Np; ++j)
			{
				double A = kinEl->A[j];
				double nf = kinEl->NodalnFriction[j];
				double S0 = kinEl->dz[j];
				double H = A/weq;

				// using manning's N
				double myF = weq*sqrt(S0)*pow(H,5.0/3)/nf;

				// using Chezy's relationship (nf stands for C)
				//double myF = weq*sqrt(S0*H)*H*nf;

				if (S0 < 0 )
					printf("z1 = %lf z2 = %lf\n", kinEl->z1, kinEl->z2);
				if (isnan(myF))
				{
					printf("myF is nan; A = %lf, nf = %lf, S0 = %lf, H = %lf weq = %lf, kinElNum = %d\n", A, nf, S0, H, weq, i);
					exit(1);
				}
				gsl_vector_set(F, j, myF);

				double rainfall;
				if (time < 1800)
					rainfall = 1.0/12/3600;

				gsl_vector_set(ST, j, weq*rainfall);
				// second parking lot case
				//if (time  <= 180)
				//	gsl_vector_set(ST, j, weq*2.0/12/3600);
				//else if (time <= 360)
				//	gsl_vector_set(ST, j, weq*4.0/12/3600);

			}
			// Now calculate total upstream flux.
			// If the element is at the boundary of the watershed, flux coming in will be 0.
			int numUpstreamEls = kinEl->numUpstreamEls;
			double Fhat1 = 0.0;
			//if (numUpstreamEls > 1)
			//	printf(" i = %d UpEl1 = %d UpEl2 = %d\n", i, kinEl->upstreamEls[0], kinEl->upstreamEls[1]);
			for (int j = 0; j < numUpstreamEls; ++j)
			{
				int upstreamEl = kinEl->upstreamEls[j];
				//printf("upstream el = %d\t", upstreamEl);
				Fhat1 += Fhat[upstreamEl];
			}
			//printf("downstream el = %d\n", kinEl->el2);
			double Fhat2 = Fhat[i];
			gsl_vector *localFhat = gsl_vector_alloc(2);
			gsl_vector_set(localFhat, 0, Fhat1);
			gsl_vector_set(localFhat, 1, Fhat2);

			gsl_vector *localRHS = gsl_vector_calloc(Np);

			// calculate the volume integral
			gsl_blas_dgemv(CblasNoTrans, 2.0/h_el, VolMat, F, 1.0, localRHS);

			// calculate the surface integral
			gsl_vector *SurfPart = gsl_vector_calloc(Np);
			gsl_blas_dgemv(CblasNoTrans, 2.0/h_el, LIFT, localFhat, 1.0, SurfPart);

			// subtract the surface integral from the volume integral
			gsl_vector_add(localRHS, SurfPart);

			// add source term to the right hand side

			gsl_vector_add(localRHS, ST);

			for (int j = 0; j < Np; j++)
			{
				RHS[i*Np+j] = gsl_vector_get(localRHS, j);
				if (isnan(RHS[i*Np+j]))
				{
					printf("SurfPart = %lf \n", gsl_vector_get(SurfPart,j));
				}
			}
			gsl_vector_free(F);
			gsl_vector_free(localFhat);
			gsl_vector_free(SurfPart);
			gsl_vector_free(localRHS);
			gsl_vector_free(ST);

		}

	}
	free(Fhat);

}

/***********************************************************************************************//**
 * Function for evaluating the right hand side of the discrete ODE obtained from the DG discretization
 * of the 2-D shallow water equations
 * @param [in] currRegion a pointer to the junction structure corresponding to the junction that is
 * currently being worked on
 * @param [in] time  a double representing the current time of the simulation
 * @param [out] RHSZeta a pointer to an array of size NumEl x 3 in which the right hand side for
 * the water surface elevation will be stored
 * @param [out] RHSQx a pointer to an array of size NumEl x 3 in which the right hand side for the
 * momentum in the x-direction is stored
 * @param [out] RHSQy a pointer to an array of size NumEl x 3 in which the right hand side for the
 * momentum in the y-direction is stored
 *
 ***************************************************************************************************/

void compute2DL(struct TwoDRegion *currRegion, double time, double *RHSZeta, double *RHSQx, double *RHSQy, double dt)
{

	int NumEdges = currRegion->TotalNumEdges;
	int NumEl = currRegion->NumEl;
	int Nfp = currRegion->P + 1;
	int Np = currRegion->Np;

#ifdef WDON
	/**** Compute the mass over an element to use later for ensuring ********/
	/**** positive mass with the flux in a wetting and drying treatment *************/
	double* mass = xcalloc(NumEl, sizeof(double));
	gsl_vector *height = gsl_vector_calloc(Np);
	gsl_vector *tmp = gsl_vector_alloc(Np);
	gsl_vector *jac = gsl_vector_alloc(Np);

	for (int i = 0; i < NumEl; ++i)
	{
		for (int j = 0; j < Np; j++)
		{
			gsl_vector_set(height, j, currRegion->zeta[i][j] + currRegion->NodalZ[i][j]);
			gsl_vector_set(jac, j, currRegion->jac[i]);
		}

		gsl_blas_dgemv(CblasNoTrans, 1.0, MassMatrix2D, height, 0.0, tmp);
		gsl_blas_ddot(jac, tmp, &mass[i]);

	}

	gsl_vector_free(height);
	gsl_vector_free(tmp);
	gsl_vector_free(jac);
#endif

	/********************************* Compute Roe's flux at the face **********************************************/
	double **Fhat1dotn, **Fhat2dotn, **Fhat3dotn;
	Fhat1dotn = malloc(NumEl*sizeof(double*));
	Fhat2dotn = malloc(NumEl*sizeof(double*));
	Fhat3dotn = malloc(NumEl*sizeof(double*));
	for (int i = 0; i < NumEl; i++)
	{
		Fhat1dotn[i] = xcalloc(3*Nfp, sizeof(double));
		Fhat2dotn[i] = xcalloc(3*Nfp, sizeof(double));
		Fhat3dotn[i] = xcalloc(3*Nfp, sizeof(double));
	}

	for (int i=0; i<NumEdges; ++i)
	{
		int el1 = currRegion->EdgtoEls[i*2];
		int el2 = currRegion->EdgtoEls[i*2+1];

		int ledg1 = currRegion->GlobaltoLocalEdg[i*2];
		int ledg2 = currRegion->GlobaltoLocalEdg[i*2+1];

		// take the normal from element 1 side
		double nx = currRegion->nx[el1*3+ledg1];
		double ny = currRegion->ny[el1*3+ledg1];

		double tx = -ny;
		double ty = nx;

		for (int j = 0; j < Nfp; j++)
		{
			double zeta_in, Qx_in, Qy_in;
			double zeta_ex, Qx_ex, Qy_ex;
			double z_in, z_ex;			// should be the same as z_ex

			int lv1 = currRegion->GlobalEdgPosNegNodes[i][2*j];
			int lv2 = currRegion->GlobalEdgPosNegNodes[i][2*j+1];

			int pos1 = currRegion->PosInFVec[i][2*j];
			int pos2 = currRegion->PosInFVec[i][2*j+1];

			double Fn_in[3], Fn_ex[3];

			zeta_in = currRegion->zeta[el1][lv1];
			Qx_in = currRegion->Qx[el1][lv1];
			Qy_in = currRegion->Qy[el1][lv1];
			z_in = currRegion->NodalZ[el1][lv1];

			double Q_T_in = Qx_in*tx + Qy_in*ty;

			// need to change this later so that the boundary condition is given per node on the edge
			// if the edge is an exterior edge connected to a channel
			int bdrypres = currRegion->BdryPrescribed[i];

		//if (el1 == 531 || el2 == 531)
		//{
		//	printf("el1 = %d, el2 = %d\n", el1, el2);
		//	printf("WD1 = %d, WD2 = %d\n", currRegion->WD[el1], currRegion->WD[el2]);
		//	printf("bdrypres = %d\n", bdrypres);
		//}

			// manufactured solution
			if (bdrypres == 555)
				//if (bdrypres == 555 || (el1 == el2))
			{
				double xval = currRegion->NodalX[el1][lv1];
				double yval = currRegion->NodalY[el1][lv1];
				zeta_ex = getmanH(xval, yval, time);
				Qx_ex = getQx(xval, yval, time);
				Qy_ex = getQy(xval, yval, time);
			}
			// if inflow or outflow boundary, i.e. if bdrypres = 1 or 2
			//for floodplains
			else if (bdrypres == 111)
			{
				zeta_ex = currRegion->bzeta[i];
				Qx_ex = Qx_in;
				Qy_ex = Qy_in;
			}

			else if (bdrypres == 222)
			{
				zeta_ex = zeta_in;
				double Q_N_ex = currRegion->bQn[i];
				double Q_T_ex = Q_T_in;
				double denom = 1./(nx*ty-ny*tx);
				Qx_ex = (ty*Q_N_ex - ny*Q_T_ex)*denom;
				Qy_ex = (-tx*Q_N_ex + nx*Q_T_ex)*denom;
			}

			else if (bdrypres == 333)
			{
				zeta_ex = currRegion->bQn[i];
				double Q_N_ex = currRegion->bQn[i];
				double Q_T_ex = Q_T_in;
				double denom = 1./(nx*ty-ny*tx);
				Qx_ex = (ty*Q_N_ex - ny*Q_T_ex)*denom;
				Qy_ex = (-tx*Q_N_ex + nx*Q_T_ex)*denom;

			}
			// for junctions
			else if (bdrypres == 1 || bdrypres == 2)
			{
				zeta_ex = currRegion->bzeta[i];
				double Q_N_ex = currRegion->bQn[i];
				// for the first iteration
				if (zeta_ex == -10000.0 && Q_N_ex == -10000.0)
				{
					zeta_ex = zeta_in;
					Qx_ex = Qx_in;
					Qy_ex = Qy_in;
					z_ex = z_in;
				}
				else
				{
					double Q_T_ex = Q_T_in;
					double denom = 1./(nx*ty-ny*tx);
					Qx_ex = (ty*Q_N_ex - ny*Q_T_ex)*denom;
					Qy_ex = (-tx*Q_N_ex + nx*Q_T_ex)*denom;
					z_ex = z_in;
				}
			}

			// for floodplains
			else if (bdrypres == 3)
			{
				double Q_N_ex = receive_floodwater(currRegion, i);

				// element 1
				Qx_in = currRegion->Qx[el1][lv1];
				Qy_in = currRegion->Qy[el1][lv1];
				zeta_in = currRegion->zeta[el1][lv1];
				z_in = currRegion->NodalZ[el1][lv1];
				zeta_ex = zeta_in;
				Q_T_in = Qx_in*tx + Qy_in*ty;
				double Q_T_ex = Q_T_in;
				double denom = 1./(nx*ty-ny*tx);
				Qx_ex = (ty*Q_N_ex - ny*Q_T_ex)*denom;
				Qy_ex = (-tx*Q_N_ex + nx*Q_T_ex)*denom;

				double localG = g;

#ifdef WDON
				if (currRegion->WD[el1] == 0)
					localG = 0;
				else
					localG = g;
#endif

				double current_max_lam= RoeFluxJunc(zeta_in, zeta_ex, Qx_in, Qx_ex, Qy_in, Qy_ex, z_in, nx, ny, localG,Fn_in);

				//if (el1 == 170 && fabs(Q_N_ex) > 0)
				//{
				//	printf("***************\n");
				//	printf("time = %lf\n", time);
				//	printf("j = %d el1 = %d, el2 = %d\n", j, el1, el2);
				//	printf("WD1 = %d, WD2 = %d\n", currRegion->WD[el1], currRegion->WD[el2]);
				//	printf("Q_N_ex = %lf, Qx_in = %lf, Qy_in = %lf, \n", Q_N_ex, Qx_in, Qy_in);
				//	printf("Fn_in = %lf, %lf, %lf\n", Fn_in[0], Fn_in[1], Fn_in[2]);
				//}

				// element 2
				nx = -nx;
				ny = -ny;
				tx = -ny;
				ty = nx;
				denom = 1./(nx*ty-ny*tx);
				zeta_in = currRegion->zeta[el2][lv2];
				z_in = currRegion->NodalZ[el2][lv2];
				zeta_ex = zeta_in;
				Qx_in = currRegion->Qx[el2][lv2];
				Qy_in = currRegion->Qy[el2][lv2];
				Q_T_in = Qx_in*tx + Qy_in*ty;
				Q_T_ex = Q_T_in;
				denom = 1./(nx*ty-ny*tx);
				Qx_ex = (ty*Q_N_ex - ny*Q_T_ex)*denom;
				Qy_ex = (-tx*Q_N_ex + nx*Q_T_ex)*denom;
				z_ex = z_in;

#ifdef WDON
				if (currRegion->WD[el2] == 0)
					localG = 0;
				else
					localG = g;
#endif

				current_max_lam= RoeFluxJunc(zeta_in, zeta_ex, Qx_in, Qx_ex, Qy_in, Qy_ex, z_in, nx, ny, localG, Fn_ex);

				if (el2 == 170 && fabs(Q_N_ex)>0)
				{
					printf("***************\n");
					printf("el1 = %d, el2 = %d\n", el1, el2);

					printf("Q_N_ex = %lf\n", Q_N_ex);
					printf("Fn_in = %lf, %lf, %lf\n", Fn_in[0], Fn_in[1], Fn_ex[2]);
				}
			}

			// if edge i is a boundary edge but not connected to a channel, implement no flux boundary condition
			else if (el1 == el2)
			{
				zeta_ex  = currRegion->zeta[el2][lv2];
				// Compute the velocity in the normal direction
				double Q_N_in = Qx_in*nx + Qy_in*ny;
				double Q_T_in = Qx_in*tx + Qy_in*ty;

				// Reflect the velocity in the normal direction
				double Q_N_ex = -Q_N_in;
				double Q_T_ex = Q_T_in;

				// Compute the x and y components of the external state flow
				double denom = 1./(nx*ty - ny*tx);
				Qx_ex = (ty*Q_N_ex - ny*Q_T_ex)*denom;
				Qy_ex = (-tx*Q_N_ex + nx*Q_T_ex)*denom;

				z_ex = z_in;

			}

			// if the edge is not a boundary edge
			else
			{
				zeta_ex = currRegion->zeta[el2][lv2];
				Qx_ex = currRegion->Qx[el2][lv2];
				Qy_ex = currRegion->Qy[el2][lv2];
				z_ex = currRegion->NodalZ[el2][lv2];
			}

#ifdef WDON
			// Check to see if both of the elements separated by this edge are dry
			if (currRegion->WD[el1] == 0 && currRegion->WD[el2] == 0 && (bdrypres != 3) && (bdrypres != 222) && (bdrypres != 2) && (bdrypres !=1))
			{
				Fn_in[0] = 0.0;
				Fn_in[1] = 0.0;
				Fn_in[2] = 0.0;
				Fn_ex[0] = 0.0;
				Fn_ex[1] = 0.0;
				Fn_ex[2] = 0.0;

				//// Reflection flux for the interior element
				//double zeta_ex_ref = zeta_in;
				//double Q_N_in = Qx_in*nx + Qy_in*ny;
				//double Q_T_in = Qx_in*tx + Qy_in*ty;

				//double Q_N_ex = -Q_N_in;
				//double Q_T_ex = Q_T_in;
				//
				//double denom = 1./(nx*ty - ny*tx);
				//double Qx_ex_ref = (ty*Q_N_ex - ny*Q_T_ex)*denom;
				//double Qy_ex_ref = (-tx*Q_N_ex + nx*Q_T_ex)*denom;

				//double max_lam_in = RoeFluxJunc(zeta_in, zeta_ex_ref, Qx_in,Qx_ex_ref,
				//	Qy_in, Qy_ex_ref, z_in, nx, ny, 0, Fn_in);

				//// Reflection flux for the exterior element
				//double zeta_in_ref = zeta_ex;
				//Q_N_ex = Qx_ex*nx + Qy_ex*ny;
				//Q_T_ex = Qx_ex*tx + Qy_ex*ty;

				//Q_N_in = -Q_N_ex;
				//Q_T_in = Q_T_ex;
				//
				//double Qx_in_ref = (ty*Q_N_in - ny*Q_T_in)*denom;
				//double Qy_in_ref = (-tx*Q_N_in + nx*Q_T_ex)*denom;
				//double max_lam_ex = RoeFluxJunc(zeta_in_ref, zeta_ex, Qx_in_ref, Qx_ex, Qy_in_ref, Qy_ex, z_in, nx, ny, 0, Fn_ex);

				//if (i ==0)
				//	currRegion->max_lambda = max(max_lam_in, max_lam_ex);
				//else
				//{
				//	currRegion->max_lambda = fmax(currRegion->max_lambda, max_lam_in);
				//	currRegion->max_lambda = fmax(currRegion->max_lambda, max_lam_ex);
				//}

			}
			else if (bdrypres != 3 && bdrypres != 222)// if the elements aren't both dry
			{
				double Fhatdotn[3];
				double current_max_lam= RoeFluxJunc(zeta_in, zeta_ex, Qx_in, Qx_ex, Qy_in, Qy_ex, z_in, nx, ny, g,Fhatdotn);

				if (isnan(Fhatdotn[0]) || isnan(Fhatdotn[1]) || isnan(Fhatdotn[2]))
				{
					printf("2D both elements wet flux not a number, edge %d, time %e, for domain type %d \n",i,time, currRegion->type);
					printf("ze_ex = %lf, ze_in = %lf, z_in = %lf, Qx_ex = %lf, Qx_in = %lf, Qy_ex = %lf, Qy_in = %lf, el_ex = %d, el_in = %d, bdrypres = %d\n", zeta_ex, zeta_in, z_in, Qx_ex, Qx_in, Qy_ex, Qy_in, el2, el1, bdrypres);
					printf("wdfalg1 = %d , wdflag2 = %d\n", currRegion->WD[el1], currRegion->WD[el2]);
					exit(EXIT_FAILURE);
				}

				// Check to see if the flux is large enough to dry up the elements
				// Calculate the sum of the length of the three edges of the interior and exterior element
				double edg_len_el1 = 0;
				for (int v = 0; v < 3; v++)
				{
					int v1 = currRegion->EltoVert[el1*3 + v];
					int v2 = currRegion->EltoVert[el1*3 + (v+1)%3];
					edg_len_el1 += sqrt(pow(currRegion->Vx[v1] - currRegion->Vx[v2], 2) + pow(currRegion->Vy[v1] - currRegion->Vy[v2],2));
				}

				double edg_len_el2 = 0;
				for (int v = 0; v < 3; v++)
				{
					int v1 = currRegion->EltoVert[el2*3 + v];
					int v2 = currRegion->EltoVert[el2*3 + (v+1)%3];
					edg_len_el2 += sqrt(pow(currRegion->Vx[v1] - currRegion->Vx[v2], 2) + pow(currRegion->Vy[v1] - currRegion->Vy[v2],2));
				}

				double maxBetaOverAlpha = 2;

				if ((1.01*Fhatdotn[0]*edg_len_el1*maxBetaOverAlpha*dt >= mass[el1]) || (-1.01*Fhatdotn[0]*edg_len_el2*maxBetaOverAlpha*dt >= mass[el2]))
				{
					//printf("either el %d or el %d will be dry \n", el1, el2);
					Fn_in[0] = 0.0;
					Fn_in[1] = 0.0;
					Fn_in[2] = 0.0;
					Fn_ex[0] = 0.0;
					Fn_ex[1] = 0.0;
					Fn_ex[2] = 0.0;

				}

				else // check to make sure the flux is not coming from a dry element
				{
					if (currRegion->WD[el1] == 1 || (el1 == el2))
					{
						for (int j =0; j < 3; ++j)
						{
							Fn_in[j] = Fhatdotn[j];
						}
					}
					else if (Fhatdotn[0] > 0)
					{
						//printf("initially, el1 = %d, j = %d, Fn_ex = %lf\n", el1, j, Fhatdotn[0]);
						zeta_ex = currRegion->zeta[el2][lv2];
						zeta_in = zeta_ex;
						Qx_ex = currRegion->Qx[el2][lv2];
						Qy_ex = currRegion->Qy[el2][lv2];
						double Q_N_ex = Qx_ex*nx + Qy_ex*ny;
						double Q_N_in = -Q_N_ex;
						double Q_T_ex = Qx_ex*tx + Qy_ex*ty;
						Q_T_in = Q_T_ex;
						double denom = 1./(nx*ty-ny*tx);
						Qx_in = (ty*Q_N_in - ny*Q_T_in)*denom;
						Qy_in = (-tx*Q_N_in + nx*Q_T_in)*denom;

						double max_lam_in = RoeFluxJunc(zeta_in, zeta_ex, Qx_in, Qx_ex, Qy_in, Qy_ex,
								z_ex, nx, ny, g, Fn_in);
						if (isnan(Fn_in[0]) || isnan(Fn_in[1]) || isnan(Fn_in[2]))
						{
							printf("2D interior element dry flux not a number, edge %d, el_in = %d, el_ex = %d, domain type = %d\n",i, el1, el2, currRegion->type);
							printf("ze_ex = %lf, ze_in = %lf, z_in = %lf, z_ex = %lf, Qx_ex = %lf, Qx_in = %lf, Qy_ex = %lf, Qy_in = %lf, el_ex = %d, el_in = %d, bdrypres = %d\n", zeta_ex, zeta_in, z_in, z_ex, Qx_ex, Qx_in, Qy_ex, Qy_in, el2, el1, bdrypres);
							printf("wdfalg1 = %d , wdflag2 = %d\n", currRegion->WD[el1], currRegion->WD[el2]);
							exit(EXIT_FAILURE);
						}
						//printf("after reflection, el1 = %d, Fn_ex = %lf\n", el1, Fn_in[0]);
						current_max_lam = max(current_max_lam, max_lam_in);
					}

					else
					{
						double max_lam_in = RoeFluxJunc(zeta_in, zeta_ex, Qx_in, Qx_ex, Qy_in, Qy_ex,
								z_in, nx, ny, 0, Fn_in);

					}

					if (currRegion->WD[el2] == 1 && (el1 != el2))
					{
						for (int j = 0; j < 3; ++j)
						{
							Fn_ex[j] = Fhatdotn[j];
						}
					}
					else if ((el1 != el2) && (Fhatdotn[0] < 0))
					{
					//	printf("initially, el2 = %d, Fn_ex = %lf\n", el2, -Fhatdotn[0]);
						Qx_in = currRegion->Qx[el1][lv1];
						Qy_in = currRegion->Qy[el1][lv1];
						zeta_in = currRegion->zeta[el1][lv1];
						double Q_N_in = Qx_in*nx + Qy_in*ny;
						double Q_T_in = Qx_in*tx + Qy_in*ty;
						double Q_N_ex = -Q_N_in;
						double Q_T_ex = Q_T_in;
						double denom = 1.0/(nx*ty - ny*tx);
						Qx_ex = (ty*Q_N_ex - ny*Q_T_ex)*denom;
						Qy_ex = (-tx*Q_N_ex + nx*Q_T_ex)*denom;
						zeta_ex = zeta_in;

						double max_lam_ex = RoeFluxJunc(zeta_in, zeta_ex, Qx_in, Qx_ex,
								Qy_in, Qy_ex, z_in, nx, ny, g, Fn_ex);
						current_max_lam = max(current_max_lam, max_lam_ex);

						if (isnan(Fn_ex[0]) || isnan(Fn_ex[1]) || isnan(Fn_ex[2]))
						{
							printf("2D exterior element dry flux not a number, edge %d \n",i);
							exit(EXIT_FAILURE);
						}
						//printf("el2 = %d, Fn_ex = %lf\n", el2, -Fn_ex[0]);

					//	printf("after reflection, el2 = %d, Fn_ex = %lf\n", el2, -Fn_ex[0]);
					}
					else if (el1 != el2 )
					{
						double max_lam_ex = RoeFluxJunc(zeta_in, zeta_ex, Qx_in, Qx_ex,
								Qy_in, Qy_ex, z_in, nx, ny, 0, Fn_ex);
						current_max_lam = max(current_max_lam, max_lam_ex);

					}

					if (i == 0)
						currRegion->max_lambda = current_max_lam;
					else
						currRegion->max_lambda = fmax(currRegion->max_lambda, current_max_lam);
				}

			}

#else

			double Fhatdotn[3];
			double current_max_lam= RoeFluxJunc(zeta_in, zeta_ex, Qx_in, Qx_ex, Qy_in, Qy_ex, z_in, nx, ny, g, Fhatdotn);

			if (isnan(Fhatdotn[0]) || isnan(Fhatdotn[1]) || isnan(Fhatdotn[2]))
			{
				printf("2D numerical flux not a number, edge %d \n",i);
				printf("zeta_in = %e, zeta_ex = %e, z_edge = %e, BdryPrescribed = %d \n", zeta_in, zeta_ex, z_in, bdrypres);
				exit(EXIT_FAILURE);
			}

			for (int j = 0; j < 3; ++j)
			{
				Fn_in[j] = Fhatdotn[j];
				Fn_ex[j] = Fhatdotn[j];
			}

			if (i == 0)
				currRegion->max_lambda = current_max_lam;
			else
				currRegion->max_lambda = max(currRegion->max_lambda, current_max_lam);

#endif

			// store Fhatdotn for the two elements connected by this edge
			Fhat1dotn[el1][pos1] = Fn_in[0];
			Fhat2dotn[el1][pos1] = Fn_in[1];
			Fhat3dotn[el1][pos1] = Fn_in[2];

			// store the value for the exterior element, only if the edge is not a boundary edge
			if (el2 != el1 && (bdrypres != 3))
			{
				Fhat1dotn[el2][pos2] = -Fn_ex[0];
				Fhat2dotn[el2][pos2] = -Fn_ex[1];
				Fhat3dotn[el2][pos2] = -Fn_ex[2];
			}
			else if (el2 != el1)  // for bdyrpes = 3, we have already taken sign of normals into account during flux calculation
			{
				Fhat1dotn[el2][pos2] = Fn_ex[0];
				Fhat2dotn[el2][pos2] = Fn_ex[1];
				Fhat3dotn[el2][pos2] = Fn_ex[2];
			}

		} // end node loop

	}	// end edge loop

	for (int k=0; k<NumEl; ++k)
	{
		gsl_vector *F1x = gsl_vector_alloc(Np);
		gsl_vector *F2x = gsl_vector_alloc(Np);
		gsl_vector *F3x = gsl_vector_alloc(Np);
		gsl_vector *F1y = gsl_vector_alloc(Np);
		gsl_vector *F2y = gsl_vector_alloc(Np);
		gsl_vector *F3y = gsl_vector_alloc(Np);
		gsl_vector *ST21 = gsl_vector_calloc(Np);
		gsl_vector *ST22 = gsl_vector_calloc(Np);
		gsl_vector *ST31 = gsl_vector_calloc(Np);
		gsl_vector *ST32 = gsl_vector_calloc(Np);
		//gsl_vector *ST11 = gsl_vector_calloc(Np);

		double localG = g;
#ifdef WDON
		if(currRegion->WD[k] == 0)
			localG = 0;
#endif

		for (int i = 0; i < Np; i++)
		{
			double zeta = currRegion->zeta[k][i];
			double Qx = currRegion->Qx[k][i];
			double Qy = currRegion->Qy[k][i];
			double z = currRegion->NodalZ[k][i];
			double H = zeta + z;
			double f2x = Qx*Qx/H + 0.5*localG*(H*H - z*z);
			double f2y = Qx*Qy/H;
			double f3x = f2y;
			double f3y = Qy*Qy/H + 0.5*localG*(H*H - z*z);
			gsl_vector_set(F1x, i, Qx);
			gsl_vector_set(F1y, i, Qy);
			gsl_vector_set(F2x, i, f2x);
			gsl_vector_set(F2y, i, f2y);
			gsl_vector_set(F3x, i, f3x);
			gsl_vector_set(F3y, i, f3y);

			double u = Qx/H;
			double v = Qy/H;
			double nfric = currRegion->NodalnFriction[k][i];
			double tau = localG*nfric*nfric*sqrt(u*u + v*v)/pow(H,4.0/3);
			double dzx = currRegion->Nodaldzx[k][i];
			double dzy = currRegion->Nodaldzy[k][i];

			gsl_vector_set(ST21, i, localG*zeta*dzx);
			gsl_vector_set(ST22, i, -tau*Qx);
			gsl_vector_set(ST31, i, localG*zeta*dzy);
			gsl_vector_set(ST32, i, -tau*Qy);

			// Source terms for manufactured solution
			//double xval = currRegion->NodalX[k][i];
			//double yval = currRegion->NodalY[k][i];

			//double st2 = getS2(xval, yval, time);
			//double st3 = getS3(xval, yval, time);
			//gsl_vector_set(ST21, i, st2);
			//gsl_vector_set(ST31, i, st3);

#ifdef WDON
			if (currRegion->WD[k] == 0)
			{
				gsl_vector_set(F1x, i, 0);
				gsl_vector_set(F1y, i, 0);
				gsl_vector_set(F2x, i, 0);
				gsl_vector_set(F2y, i, 0);
				gsl_vector_set(F3x, i, 0);
				gsl_vector_set(F3y, i, 0);
				gsl_vector_set(ST21, i, 0);
				gsl_vector_set(ST22, i, 0);
				gsl_vector_set(ST31, i, 0);
				gsl_vector_set(ST32, i, 0);
			}
#endif

		// rainfall
		//if (currRegion->type == 2)
		//	gsl_vector_set(ST11, i, 0.5*12/3600);

		}

		// Calculate the volume integral of the flux
		double rx = currRegion->rx[k];
		double ry = currRegion->ry[k];
		double sx = currRegion->sx[k];
		double sy = currRegion->sy[k];

		gsl_vector *localRHS1 = gsl_vector_calloc(Np);
		gsl_vector *localRHS2 = gsl_vector_calloc(Np);
		gsl_vector *localRHS3 = gsl_vector_calloc(Np);

		gsl_vector *lR12 = gsl_vector_calloc(Np);
		gsl_vector *lR13 = gsl_vector_calloc(Np);
		gsl_vector *lR14 = gsl_vector_calloc(Np);
		gsl_vector *lR22 = gsl_vector_calloc(Np);
		gsl_vector *lR23 = gsl_vector_calloc(Np);
		gsl_vector *lR24 = gsl_vector_calloc(Np);
		gsl_vector *lR32 = gsl_vector_calloc(Np);
		gsl_vector *lR33 = gsl_vector_calloc(Np);
		gsl_vector *lR34 = gsl_vector_calloc(Np);

		gsl_blas_dgemv(CblasNoTrans, rx, Drw, F1x, 0.0, localRHS1);
		gsl_blas_dgemv(CblasNoTrans, sx, Dsw, F1x, 0.0, lR12);
		gsl_blas_dgemv(CblasNoTrans, ry, Drw, F1y, 0.0, lR13);
		gsl_blas_dgemv(CblasNoTrans, sy, Dsw, F1y, 0.0, lR14);

		gsl_vector_add(localRHS1, lR12);
		gsl_vector_add(localRHS1, lR13);
		gsl_vector_add(localRHS1, lR14);

		gsl_blas_dgemv(CblasNoTrans, rx, Drw, F2x, 0.0, localRHS2);
		gsl_blas_dgemv(CblasNoTrans, sx, Dsw, F2x, 0.0, lR22);
		gsl_blas_dgemv(CblasNoTrans, ry, Drw, F2y, 0.0, lR23);
		gsl_blas_dgemv(CblasNoTrans, sy, Dsw, F2y, 0.0, lR24);

		gsl_vector_add(localRHS2, lR22);
		gsl_vector_add(localRHS2, lR23);
		gsl_vector_add(localRHS2, lR24);

		gsl_blas_dgemv(CblasNoTrans, rx, Drw, F3x, 0.0, localRHS3);
		gsl_blas_dgemv(CblasNoTrans, sx, Dsw, F3x, 0.0, lR32);
		gsl_blas_dgemv(CblasNoTrans, ry, Drw, F3y, 0.0, lR33);
		gsl_blas_dgemv(CblasNoTrans, sy, Dsw, F3y, 0.0, lR34);

		gsl_vector_add(localRHS3, lR32);
		gsl_vector_add(localRHS3, lR33);
		gsl_vector_add(localRHS3, lR34);

		// calculate the surface integral of the flux
		gsl_vector *localFhat1dotn = gsl_vector_alloc(3*Nfp);
		gsl_vector *localFhat2dotn = gsl_vector_alloc(3*Nfp);
		gsl_vector *localFhat3dotn = gsl_vector_alloc(3*Nfp);

		for (int i = 0; i < 3; i++)
		{
			double edgJac = currRegion->edgJac[k*3+i];
			for (int j = 0; j < Nfp; j++)
			{
				int index = i*Nfp+j;
				gsl_vector_set(localFhat1dotn, index, edgJac*Fhat1dotn[k][index]);
				gsl_vector_set(localFhat2dotn, index, edgJac*Fhat2dotn[k][index]);
				//printf("edgJac = %lf Fhat = %lf\n", edgJac, Fhat2dotn[k][index]);
				gsl_vector_set(localFhat3dotn, index, edgJac*Fhat3dotn[k][index]);

			}
		}

		double jac = currRegion->jac[k];

		gsl_vector *SurfPart1 = gsl_vector_calloc(Np);
		gsl_vector *SurfPart2 = gsl_vector_calloc(Np);
		gsl_vector *SurfPart3 = gsl_vector_calloc(Np);

		double fac = 1.0/jac;
		gsl_blas_dgemv(CblasNoTrans, fac, LIFT2D, localFhat1dotn, 0.0, SurfPart1);
		gsl_blas_dgemv(CblasNoTrans, fac, LIFT2D, localFhat2dotn, 0.0, SurfPart2);
		gsl_blas_dgemv(CblasNoTrans, fac, LIFT2D, localFhat3dotn, 0.0, SurfPart3);

		// subtract this from the RHS
		gsl_vector_sub(localRHS1, SurfPart1);
		gsl_vector_sub(localRHS2, SurfPart2);
		gsl_vector_sub(localRHS3, SurfPart3);

		// add source and sink terms
		//gsl_vector_add(localRHS1, ST11);
		//
		//gsl_vector_add(localRHS1, ST11);
		gsl_vector_add(localRHS2, ST21);
		gsl_vector_add(localRHS2, ST22);
		gsl_vector_add(localRHS3, ST31);
		gsl_vector_add(localRHS3, ST32);

		int begNode = k*Np;
		for(int i = 0; i < Np; i++)
		{
			RHSZeta[begNode+i] = gsl_vector_get(localRHS1,i);
			RHSQx[begNode+i] = gsl_vector_get(localRHS2,i);
			RHSQy[begNode+i] = gsl_vector_get(localRHS3, i);

		}

		// free all gsl vectors
		gsl_vector_free(localRHS1);
		gsl_vector_free(localRHS2);
		gsl_vector_free(localRHS3);
		gsl_vector_free(localFhat1dotn);
		gsl_vector_free(localFhat2dotn);
		gsl_vector_free(localFhat3dotn);
		gsl_vector_free(SurfPart1);
		gsl_vector_free(SurfPart2);
		gsl_vector_free(SurfPart3);
		gsl_vector_free(lR12);
		gsl_vector_free(lR13);
		gsl_vector_free(lR14);
		gsl_vector_free(lR22);
		gsl_vector_free(lR23);
		gsl_vector_free(lR24);
		gsl_vector_free(lR32);
		gsl_vector_free(lR33);
		gsl_vector_free(lR34);
		gsl_vector_free(F1x);
		gsl_vector_free(F2x);
		gsl_vector_free(F3x);
		gsl_vector_free(F1y);
		gsl_vector_free(F2y);
		gsl_vector_free(F3y);
		//gsl_vector_free(ST11);
		gsl_vector_free(ST21);
		gsl_vector_free(ST22);
		gsl_vector_free(ST31);
		gsl_vector_free(ST32);

	}

	// free allocated space
	for (int i = 0; i < NumEl; i++)
	{
		free(Fhat1dotn[i]);
		free(Fhat2dotn[i]);
		free(Fhat3dotn[i]);
	}
	free(Fhat1dotn);
	free(Fhat2dotn);
	free(Fhat3dotn);

#ifdef WDON
	free(mass);
#endif

}


#ifndef __INTERP_2D_H__
#define __INTERP_2D_H__

#include <string.h>
//#include <gsl/gsl_vector.h>
//#include <gsl/gsl_matrix.h>
//#include <gsl/gsl_math.h>
#include <gsl/gsl_spline.h>

#include "cubic_bspline_2d_coeffs.h"
#include "cubic_bspline_2d_interpol.h"

enum
{
    INTERP_2D_LINEAR=0,
    INTERP_2D_CUBIC_BSPLINE=1
};

typedef struct
{
    int size1;
    int size2;
    double * xa;
    double * ya;
    double * za;
    int type;
}interp_2d;

interp_2d * interp_2d_alloc(int size1, int size2);
void interp_2d_free(interp_2d * i2d);

void interp_2d_init(interp_2d * i2d, const double * xa, const double * ya, const double * za, int type);

double interp_2d_eval(interp_2d  * i2d, double x, double y, gsl_interp_accel * accx, gsl_interp_accel * accy);
void interp_2d_eval_grad(interp_2d * i2d, double x, double y, double * grad, gsl_interp_accel * accx, gsl_interp_accel * accy);
double interp_2d_eval_cubic_bspline(interp_2d * i2d, double x, double y, gsl_interp_accel * accx,gsl_interp_accel * accy);

#endif