/**
 * @file upsc.cpp
 * @brief implementation of hdm upscaling
 * @author Alina Yapparova
 * @version
 * @date 2012-20-02
 */

#include <iomanip>
#include <iostream>

#include <list>
#include <map>

#include "bs_kernel.h"
#include "bs_misc.h"

#include "upsc.h"
#include "conf.h"

using namespace boost;

#ifdef BSPY_EXPORTING_PLUGIN

#include <boost/python.hpp>
using namespace boost::python;

#endif //BSPY_EXPORTING_PLUGIN

namespace blue_sky
{

  upsc::upsc (bs_type_ctor_param)
    {

      sp_prop = BS_KERNEL.create_object ("prop");
      if (!sp_prop)
        {
          bs_throw_exception ("Type (prop) not refractered");
        }

    }
  upsc::upsc (const upsc& rhs)
        : bs_refcounter ()
    {
      *this = rhs;
    }
  void
  upsc::init_prop ()
    {

    }

spv_float upsc::upscale_grid_zcolumn ( t_long Nx, t_long Ny, t_long Nz, spv_float zcorn_, spv_uint layers_ )
{
    t_int k1, k2, new_k;
    t_long new_Nz, new_zcorn_size, layer_size;
    v_uint& layers = *layers_;
    v_uint::iterator lit, lit_next;
    v_float& zcorn = *zcorn_;

    new_Nz = layers.size();
    new_zcorn_size = 8*Nx*Ny*new_Nz;
    layer_size = 4*Nx*Ny;

    spv_float new_zcorn = BS_KERNEL.create_object(v_float::bs_type());
    new_zcorn->resize(new_zcorn_size);

    new_k = 0;

    for (lit=layers.begin();lit!=layers.end();lit++)
        {
            k1 = (*lit);
            lit_next = lit + 1;
            if (lit_next!=layers.end())
                k2 = (*lit_next);
            else
                k2 = Nz;
            std::copy ( &zcorn[2*k1*layer_size], &zcorn[(2*k1+1)*layer_size], &(*new_zcorn)[(new_k++)*layer_size] );
            std::copy ( &zcorn[(2*k2-1)*layer_size], &zcorn[2*k2*layer_size], &(*new_zcorn)[(new_k++)*layer_size] );
        }

   return new_zcorn;
}

bp::tuple upsc::upscale_grid ( t_long Nx, t_long Ny, t_long Nz, t_long ux, t_long uy,
                               spv_float coord_, spv_float zcorn_, spv_uint layers_ )
{
    t_int i, j, n, k1, k2, k, ind;
    t_long new_Nx, new_Ny, new_Nz, new_coord_size, new_zcorn_size, layer_size;
    v_uint& layers = *layers_;
    v_uint::iterator lit, lit_next;
    v_float& coord = *coord_;
    v_float& zcorn = *zcorn_;

    new_Nx = ceil(double(Nx)/double(ux));
    new_Ny = ceil(double(Ny)/double(uy));
    new_Nz = layers.size();
    new_coord_size = 6*(new_Nx+1)*(new_Ny+1);
    new_zcorn_size = 8*new_Nx*new_Ny*new_Nz;
    layer_size = 4*Nx*Ny;

    spv_float new_coord = BS_KERNEL.create_object(v_float::bs_type());
    new_coord->resize(new_coord_size);
    spv_float new_zcorn = BS_KERNEL.create_object(v_float::bs_type());
    new_zcorn->resize(new_zcorn_size);

    n = 0;
    for (j=0; j<new_Ny; j++)
        {
            for (i=0; i<new_Nx; i++)
                {
                    std::copy ( &coord[6*(uy*j*(Nx+1)+ux*i)], &coord[6*(uy*j*(Nx+1)+ux*i+1)], &(*new_coord)[n] );
                    n += 6;
                }
            std::copy ( &coord[6*(uy*j*(Nx+1)+Nx)], &coord[6*(uy*j*(Nx+1)+Nx+1)], &(*new_coord)[n] );
            n += 6;
        }
    for (i=0; i<new_Nx; i++)
        {
            std::copy ( &coord[6*(Ny*(Nx+1)+ux*i)], &coord[6*(Ny*(Nx+1)+ux*i+1)], &(*new_coord)[n] );
            n += 6;
        }
    std::copy ( &coord[6*(Ny*(Nx+1)+Nx)], &coord[6*(Ny*(Nx+1)+Nx+1)], &(*new_coord)[n] );

    k = 0;
    for (lit=layers.begin();lit!=layers.end();lit++)
        {
            k1 = (*lit);
            lit_next = lit + 1;
            if (lit_next!=layers.end())
                k2 = (*lit_next);
            else
                k2 = Nz;

            // tops
            for (j=0; j<new_Ny-1; j++)
                {
                    for (i=0; i<new_Nx-1; i++)
                        {
                            ind = 2*k1*layer_size + 4*uy*j*Nx;
                            (*new_zcorn)[(k++)] = zcorn[ind + 2*ux*i];
                            (*new_zcorn)[(k++)] = zcorn[ind + 2*ux*(i+1) - 1];
                        }
                    ind = 2*k1*layer_size + 4*uy*j*Nx;
                    (*new_zcorn)[(k++)] = zcorn[ind + 2*ux*(new_Nx-1)];
                    (*new_zcorn)[(k++)] = zcorn[ind + 2*Nx - 1];

                    for (i=0; i<new_Nx-1; i++)
                        {
                            ind = 2*k1*layer_size + (2*uy*(j+1)-1)*2*Nx;
                            (*new_zcorn)[(k++)] = zcorn[ind + 2*ux*i];
                            (*new_zcorn)[(k++)] = zcorn[ind + 2*ux*(i+1) - 1];
                        }
                    ind = 2*k1*layer_size + (2*uy*(j+1)-1)*2*Nx;
                    (*new_zcorn)[(k++)] = zcorn[ind + 2*ux*(new_Nx-1)];
                    (*new_zcorn)[(k++)] = zcorn[ind + 2*Nx - 1];
                }

            for (i=0; i<new_Nx-1; i++)
                {
                    ind = 2*k1*layer_size + 4*uy*(new_Ny-1)*Nx;
                    (*new_zcorn)[(k++)] = zcorn[ind + 2*ux*i];
                    (*new_zcorn)[(k++)] = zcorn[ind + 2*ux*(i+1) - 1];
                }
            ind = 2*k1*layer_size + 4*uy*(new_Ny-1)*Nx;
            (*new_zcorn)[(k++)] = zcorn[ind + 2*ux*(new_Nx-1)];
            (*new_zcorn)[(k++)] = zcorn[ind + 2*Nx - 1];

            for (i=0; i<new_Nx-1; i++)
                {
                    ind = 2*k1*layer_size + (2*Ny-1)*2*Nx;
                    (*new_zcorn)[(k++)] = zcorn[ind + 2*ux*i];
                    (*new_zcorn)[(k++)] = zcorn[ind + 2*ux*(i+1) - 1];
                }
            ind = 2*k1*layer_size + (2*Ny-1)*2*Nx;
            (*new_zcorn)[(k++)] = zcorn[ind + 2*ux*(new_Nx - 1)];
            (*new_zcorn)[(k++)] = zcorn[ind + 2*Nx - 1];

            // bottoms
            for (j=0; j<new_Ny-1; j++)
                {
                    for (i=0; i<new_Nx-1; i++)
                        {
                            ind = (2*k2-1)*layer_size + 4*uy*j*Nx;
                            (*new_zcorn)[(k++)] = zcorn[ind + 2*ux*i];
                            (*new_zcorn)[(k++)] = zcorn[ind + 2*ux*(i+1) - 1];
                        }
                    ind = (2*k2-1)*layer_size + 4*uy*j*Nx;
                    (*new_zcorn)[(k++)] = zcorn[ind + 2*ux*(new_Nx-1)];
                    (*new_zcorn)[(k++)] = zcorn[ind + 2*Nx - 1];

                    for (i=0; i<new_Nx-1; i++)
                        {
                            ind = (2*k2-1)*layer_size + (2*uy*(j+1)-1)*2*Nx;
                            (*new_zcorn)[(k++)] = zcorn[ind + 2*ux*i];
                            (*new_zcorn)[(k++)] = zcorn[ind + 2*ux*(i+1) - 1];
                        }
                    ind = (2*k2-1)*layer_size + (2*uy*(j+1)-1)*2*Nx;
                    (*new_zcorn)[(k++)] = zcorn[ind + 2*ux*(new_Nx-1)];
                    (*new_zcorn)[(k++)] = zcorn[ind + 2*Nx - 1];
                }

            for (i=0; i<new_Nx-1; i++)
                {
                    ind = (2*k2-1)*layer_size + 4*uy*(new_Ny-1)*Nx;
                    (*new_zcorn)[(k++)] = zcorn[ind + 2*ux*i];
                    (*new_zcorn)[(k++)] = zcorn[ind + 2*ux*(i+1) - 1];
                }
            ind = (2*k2-1)*layer_size + 4*uy*(new_Ny-1)*Nx;
            (*new_zcorn)[(k++)] = zcorn[ind + 2*ux*(new_Nx-1)];
            (*new_zcorn)[(k++)] = zcorn[ind + 2*Nx - 1];

            for (i=0; i<new_Nx-1; i++)
                {
                    ind = (2*k2-1)*layer_size + (2*Ny-1)*2*Nx;
                    (*new_zcorn)[(k++)] = zcorn[ind + 2*ux*i];
                    (*new_zcorn)[(k++)] = zcorn[ind + 2*ux*(i+1) - 1];
                }
            ind = (2*k2-1)*layer_size + (2*Ny-1)*2*Nx;
            (*new_zcorn)[(k++)] = zcorn[ind + 2*ux*(new_Nx - 1)];
            (*new_zcorn)[(k++)] = zcorn[ind + 2*Nx - 1];
        }
   return bp::make_tuple (new_coord, new_zcorn);
}

t_double upsc::calc_sum_dW ( t_long k1, t_long k2, t_long Nx, t_long Ny,
                                          spv_float vol_, spv_float ntg_, spv_float poro_, spv_float permx_ )
{
    v_float& vol = *vol_;
    v_float& ntg = *ntg_;
    v_float& poro = *poro_;
    v_float& permx = *permx_;

    t_long i, j, n, z1, z2, layer_size;
    t_long index[2];
    t_double p[2];
    t_double dW, sum_dW = 0, sum_vol;

    layer_size = Nx*Ny;
    z1 = k1*layer_size;
    z2 = k2*layer_size;

    for (j = 0; j < Ny; j++)
      {
        for (i = 0; i < Nx; i++)
          {
            index[0] = i + j * Nx + z1;
            index[1] = i + j * Nx + z2;

            for (n = 0; n < 2; n++)
                {
                    if (poro[index[n]  != 0])
                        p[n] = ntg[index[n]] * permx[index[n]] / poro[index[n]];
                    else
                        p[n] = 0;
                }

            sum_vol = (vol[index[0]] + vol[index[1]]);
            if (sum_vol != 0)
                dW = (p[0] - p[1]) * (p[0] - p[1]) * vol[index[0]] * vol[index[1]] / sum_vol;
            else
                dW = 0;
            sum_dW += dW;
          }
      }
    return sum_dW;
}

spv_float upsc::upscale_saturation_cube (t_long Nx, t_long Ny, t_long Nz, t_long Nz_upsc,
                                         spv_uint layers_, spv_float vol_, spv_float ntg_, spv_float poro_, spv_float sat_)
{
    t_int i, j, k, n, k1, k2, ind, z;
    t_long layer_size, new_cube_size;
    t_double sum_sat_poro_ntg_vol, sum_poro_ntg_vol;

    v_uint& layers = *layers_;
    v_float& vol = *vol_;
    v_float& ntg = *ntg_;
    v_float& poro = *poro_;
    v_float& sat = *sat_;

    spv_float new_sat = BS_KERNEL.create_object(v_float::bs_type());

    // important: XYZ order
    // index <-- (i, j, k)
    // index = i + j*Nx + k*Nx*Ny;

    layer_size = Nx * Ny;

    for (n = 0; n < Nz_upsc; ++n)
        {
            k1 = layers[n];

            if (n == Nz_upsc-1)
                k2 = Nz;
            else
                k2 = layers[n+1];

            if (k1 != k2)
            for (j = 0; j < Ny; ++j)
                for (i = 0; i < Nx; ++i)
                    {
                        sum_sat_poro_ntg_vol = 0;
                        sum_poro_ntg_vol = 0;

                        for (k = k1; k < k2; ++k)
                            {
                                z = k*layer_size;
                                ind = i + j * Nx + z;
                                sum_sat_poro_ntg_vol += sat[ind]*poro[ind]*ntg[ind]*vol[ind];
                                sum_poro_ntg_vol += poro[ind]*ntg[ind]*vol[ind];
                            }
                        ind = i + j * Nx + k1 * layer_size;
                        if (sum_sat_poro_ntg_vol != 0)
                            sat[ind] = sum_sat_poro_ntg_vol/sum_poro_ntg_vol;
                        else
                            sat[ind] = 0;
                    }

        }

    new_cube_size = Nz_upsc*layer_size;
    new_sat->resize(new_cube_size);

    i = 0;
    for ( k = 0; k < Nz_upsc; ++k)
        {
            std::copy ( &sat[k*layer_size], &sat[(k+1)*layer_size], &(*new_sat)[i*layer_size] );
            i++;
        }

    return new_sat;
}

spv_float upsc::upscale_sat_cube_xy (t_long Nx, t_long Ny, t_long ux, t_long uy, t_long Nz,
                                     spv_uint layers_, spv_float vol_, spv_float ntg_, spv_float poro_, spv_float sat_)
{
    t_int i, j, ii, jj, k, new_k, n, k1, k2, index, ind, new_ind;
    t_long new_Nx, new_Ny, new_Nz;
    t_long layer_size, new_layer_size, new_cube_size;
    t_double sum_sat_poro_ntg_vol, sum_poro_ntg_vol;

    v_uint& layers = *layers_;
    v_float& vol = *vol_;
    v_float& ntg = *ntg_;
    v_float& poro = *poro_;
    v_float& sat = *sat_;

    t_int rx, ry;
    rx = Nx % ux;
    ry = Ny % uy;
    new_Nx = ceil(double(Nx)/double(ux));
    new_Ny = ceil(double(Ny)/double(uy));
    new_Nz = layers.size();

    spv_float new_sat = BS_KERNEL.create_object(v_float::bs_type());
    new_cube_size = new_Nx*new_Ny*new_Nz;
    new_sat->resize(new_cube_size);

    // important: XYZ order
    // index <-- (i, j, k)
    // index = i + j*Nx + k*Nx*Ny;

    layer_size = Nx * Ny;
    new_layer_size = new_Nx * new_Ny;

    new_k = 0;
    for (n = 0; n < new_Nz; ++n)
      {
        k1 = layers[n];

        if (n == new_Nz-1)
            k2 = Nz;
        else
            k2 = layers[n+1];

        for (j = 0; j < new_Ny-1; ++j)
          {
            for (i = 0; i < new_Nx-1; ++i)
              {
                index  = ux*i + uy*j*Nx + k1*layer_size;
                new_ind = i + j*new_Nx + new_k*new_layer_size;

                sum_sat_poro_ntg_vol = 0;
                sum_poro_ntg_vol = 0;

                for (k = 0; k < (k2-k1); ++k)
                  {
                    for (jj = 0; jj < uy; jj++)
                      {
                        for (ii = 0; ii < ux; ii++)
                          {
                            ind = index + ii + jj*Nx + k*layer_size;
                            sum_sat_poro_ntg_vol += sat[ind]*poro[ind]*ntg[ind]*vol[ind];
                            sum_poro_ntg_vol += poro[ind]*ntg[ind]*vol[ind];
                          }
                      }
                  }

                if (sum_sat_poro_ntg_vol != 0)
                    (*new_sat)[new_ind] = sum_sat_poro_ntg_vol/sum_poro_ntg_vol;
                else
                    (*new_sat)[new_ind] = 0;
              }

            index  = ux*(new_Nx-1) + uy*j*Nx + k1*layer_size;
            new_ind = new_Nx-1 + j*new_Nx + new_k*new_layer_size;

            sum_sat_poro_ntg_vol = 0;
            sum_poro_ntg_vol = 0;

            for (k = 0; k < (k2-k1); ++k)
              {
                for (jj = 0; jj < uy; jj++)
                  {
                    for (ii = 0; ii < rx; ii++)
                      {
                        ind = index + ii + jj*Nx + k*layer_size;
                        sum_sat_poro_ntg_vol += sat[ind]*poro[ind]*ntg[ind]*vol[ind];
                        sum_poro_ntg_vol += poro[ind]*ntg[ind]*vol[ind];
                      }
                  }
              }

            if (sum_sat_poro_ntg_vol != 0)
                (*new_sat)[new_ind] = sum_sat_poro_ntg_vol/sum_poro_ntg_vol;
            else
                (*new_sat)[new_ind] = 0;
          }

        for (i = 0; i < new_Nx-1; ++i)
          {
            index  = ux*i + uy*(new_Ny-1)*Nx + k1*layer_size;
            new_ind = i + (new_Ny-1)*new_Nx + new_k*new_layer_size;

            sum_sat_poro_ntg_vol = 0;
            sum_poro_ntg_vol = 0;

            for (k = 0; k < (k2-k1); ++k)
              {
                for (jj = 0; jj < ry; jj++)
                  {
                    for (ii = 0; ii < ux; ii++)
                      {
                        ind = index + ii + jj*Nx + k*layer_size;
                        sum_sat_poro_ntg_vol += sat[ind]*poro[ind]*ntg[ind]*vol[ind];
                        sum_poro_ntg_vol += poro[ind]*ntg[ind]*vol[ind];
                      }
                  }
              }
            if (sum_sat_poro_ntg_vol != 0)
                (*new_sat)[new_ind] = sum_sat_poro_ntg_vol/sum_poro_ntg_vol;
            else
                (*new_sat)[new_ind] = 0;
          }

        index  = ux*(new_Nx-1) + uy*(new_Ny-1)*Nx + k1*layer_size;
        new_ind = new_Nx-1 + (new_Ny-1)*new_Nx + new_k*new_layer_size;

        sum_sat_poro_ntg_vol = 0;
        sum_poro_ntg_vol = 0;

        for (k = 0; k < (k2-k1); ++k)
          {
            for (jj = 0; jj < ry; jj++)
              {
                for (ii = 0; ii < rx; ii++)
                  {
                    ind = index + ii + jj*Nx + k*layer_size;
                    sum_sat_poro_ntg_vol += sat[ind]*poro[ind]*ntg[ind]*vol[ind];
                    sum_poro_ntg_vol += poro[ind]*ntg[ind]*vol[ind];
                  }
              }
          }
        if (sum_sat_poro_ntg_vol != 0)
            (*new_sat)[new_ind] = sum_sat_poro_ntg_vol/sum_poro_ntg_vol;
        else
            (*new_sat)[new_ind] = 0;

        new_k ++;
      }

    return new_sat;
}

int upsc::upscale_cubes ( t_long k1, t_long k2, t_long Nx, t_long Ny,
                          spv_float vol_, spv_float ntg_, spv_float poro_, spv_float permx_ )
{
    v_float& vol = *vol_;
    v_float& ntg = *ntg_;
    v_float& poro = *poro_;
    v_float& permx = *permx_;

    t_long i, j, index[2], z1, z2, layer_size;
    t_double vol_sum, ntg_vol_sum, poro_ntg_vol_sum, permx_ntg_vol_sum;

    layer_size = Nx*Ny;
    z1 = k1*layer_size;
    z2 = k2*layer_size;

    for (j = 0;j < Ny; j++)
      {
        for (i = 0;i < Nx; i++)
          {
            index[0] = i + j * Nx + z1;
            index[1] = i + j * Nx + z2;

            vol_sum = vol[index[0]] + vol[index[1]];
            ntg_vol_sum = ntg[index[0]]*vol[index[0]] +  ntg[index[1]]*vol[index[1]];
            poro_ntg_vol_sum = poro[index[0]]*ntg[index[0]]*vol[index[0]] + poro[index[1]]*ntg[index[1]]*vol[index[1]];
            permx_ntg_vol_sum = permx[index[0]]*ntg[index[0]]*vol[index[0]] + permx[index[1]]*ntg[index[1]]*vol[index[1]];

            vol[index[0]] = vol_sum;
            if (vol_sum != 0)
                ntg[index[0]] = ntg_vol_sum/vol_sum;
            else
                ntg[index[0]] = 0;

            if (ntg_vol_sum != 0)
                {
                    poro[index[0]] = poro_ntg_vol_sum/ntg_vol_sum;
                    permx[index[0]] = permx_ntg_vol_sum/ntg_vol_sum;
                }
            else
                {
                    poro[index[0]] = 0;
                    permx[index[0]] = 0;
                }

          }
      }

    return 0;
}

bp::tuple upsc::upscale_cubes_xy ( t_long Nx, t_long Ny, t_long Nz, t_long ux, t_long uy,
                                   spv_float vol_, spv_float ntg_, spv_float poro_)
{
    v_float& vol = *vol_;
    v_float& ntg = *ntg_;
    v_float& poro = *poro_;

    spv_float new_vol = BS_KERNEL.create_object(v_float::bs_type());
    spv_float new_ntg = BS_KERNEL.create_object(v_float::bs_type());
    spv_float new_poro = BS_KERNEL.create_object(v_float::bs_type());

    t_long new_cube_size, new_Nx, new_Ny;
    t_int rx, ry;
    new_Nx = ceil(double(Nx)/double(ux));
    new_Ny = ceil(double(Ny)/double(uy));
    rx = Nx % ux;
    ry = Ny % uy;
    new_cube_size = new_Nx*new_Ny*Nz;

    new_vol->resize(new_cube_size);
    new_ntg->resize(new_cube_size);
    new_poro->resize(new_cube_size);

    t_long i, j, ii, jj, k, layer_size, new_layer_size;
    t_long ind, index, new_ind;
    t_double vol_sum, ntg_vol_sum, poro_ntg_vol_sum;
    layer_size = Nx*Ny;
    new_layer_size = new_Nx*new_Ny;

    for (k = 0; k < Nz; k++)
      {
        for (j = 0; j < new_Ny-1; j++)
          {
            for (i = 0; i < new_Nx-1; i++)
              {
                ind  = ux*i + uy*j*Nx + k*layer_size;
                new_ind = i + j*new_Nx + k*new_layer_size;

                vol_sum = 0;
                ntg_vol_sum = 0;
                poro_ntg_vol_sum = 0;

                for (jj = 0; jj < uy; jj++)
                  {
                    for (ii = 0; ii < ux; ii++)
                      {
                        index = ind + ii;
                        vol_sum += vol[index];
                        ntg_vol_sum += ntg[index]*vol[index];
                        poro_ntg_vol_sum += poro[index]*ntg[index]*vol[index];
                      }
                    ind  += Nx;
                  }
                (*new_vol)[new_ind] = vol_sum;

                if (vol_sum != 0)
                    (*new_ntg)[new_ind] = ntg_vol_sum/vol_sum;
                else
                    (*new_ntg)[new_ind] = 0;

                if (ntg_vol_sum != 0)
                  {
                    (*new_poro)[new_ind] = poro_ntg_vol_sum/ntg_vol_sum;
                  }
                else
                  {
                    (*new_poro)[new_ind] = 0;
                  }
              }

            ind  = ux*(new_Nx-1) + uy*j*Nx + k*layer_size;
            new_ind = new_Nx-1 + j*new_Nx + k*new_layer_size;

            vol_sum = 0;
            ntg_vol_sum = 0;
            poro_ntg_vol_sum = 0;

            for (jj = 0; jj < uy; jj++)
              {
                for (ii = 0; ii < rx; ii++)
                  {
                    index = ind + ii;
                    vol_sum += vol[index];
                    ntg_vol_sum += ntg[index]*vol[index];
                    poro_ntg_vol_sum += poro[index]*ntg[index]*vol[index];
                  }
                ind  += Nx;
              }
            (*new_vol)[new_ind] = vol_sum;

            if (vol_sum != 0)
                (*new_ntg)[new_ind] = ntg_vol_sum/vol_sum;
            else
                (*new_ntg)[new_ind] = 0;

            if (ntg_vol_sum != 0)
              {
                (*new_poro)[new_ind] = poro_ntg_vol_sum/ntg_vol_sum;
              }
            else
              {
                (*new_poro)[new_ind] = 0;
              }
          }

        for (i = 0; i < new_Nx-1; i++)
          {
            ind  = ux*i + uy*(new_Ny-1)*Nx + k*layer_size;
            new_ind = i + (new_Ny-1)*new_Nx + k*new_layer_size;

            vol_sum = 0;
            ntg_vol_sum = 0;
            poro_ntg_vol_sum = 0;

            for (jj = 0; jj < ry; jj++)
              {
                for (ii = 0; ii < ux; ii++)
                  {
                    index = ind + ii;
                    vol_sum += vol[index];
                    ntg_vol_sum += ntg[index]*vol[index];
                    poro_ntg_vol_sum += poro[index]*ntg[index]*vol[index];
                  }
                ind  += Nx;
              }
            (*new_vol)[new_ind] = vol_sum;

            if (vol_sum != 0)
                (*new_ntg)[new_ind] = ntg_vol_sum/vol_sum;
            else
                (*new_ntg)[new_ind] = 0;

            if (ntg_vol_sum != 0)
              {
                (*new_poro)[new_ind] = poro_ntg_vol_sum/ntg_vol_sum;
              }
            else
              {
                (*new_poro)[new_ind] = 0;
              }
          }

        ind  = ux*(new_Nx-1) + uy*(new_Ny-1)*Nx + k*layer_size;
        new_ind = new_Nx-1 + (new_Ny-1)*new_Nx + k*new_layer_size;

        vol_sum = 0;
        ntg_vol_sum = 0;
        poro_ntg_vol_sum = 0;

        for (jj = 0; jj < ry; jj++)
          {
            for (ii = 0; ii < rx; ii++)
              {
                index = ind + ii;
                vol_sum += vol[index];
                ntg_vol_sum += ntg[index]*vol[index];
                poro_ntg_vol_sum += poro[index]*ntg[index]*vol[index];
              }
            ind  += Nx;
          }
        (*new_vol)[new_ind] = vol_sum;

        if (vol_sum != 0)
            (*new_ntg)[new_ind] = ntg_vol_sum/vol_sum;
        else
            (*new_ntg)[new_ind] = 0;

        if (ntg_vol_sum != 0)
          {
            (*new_poro)[new_ind] = poro_ntg_vol_sum/ntg_vol_sum;
          }
        else
          {
            (*new_poro)[new_ind] = 0;
          }
      }

    return bp::make_tuple(new_ntg, new_poro);
}

t_double upsc::solve_pressure_zcolumn (t_long Ny, t_long Nz, t_long i, t_long j, t_long k1, t_long k2, BS_SP(rs_mesh_iface) sp_mesh_iface)
{
    t_long size, k, n, index, ext_ind[2];
    plane_t plane[2];
    mesh_element3d element[2];
    fpoint3d_t center[2];

    t_double tz;
    t_double p_left = 1, p_right = 0;
    t_double dl, dL, dp, dP, K;

    spv_double p = BS_KERNEL.create_object(v_double::bs_type());
    spv_double rhs = BS_KERNEL.create_object(v_double::bs_type());
    spv_float tran_vals = BS_KERNEL.create_object(v_float::bs_type());
    sp_dens_mtx_t tran = BS_KERNEL.create_object("dens_matrix");
    sp_blu_solver_t solver = BS_KERNEL.create_object("blu_solver");

    size = k2 - k1 +1;

    p->resize(size);
    rhs->resize(size);

    tran->init(size, size, 60);
    tran_vals = tran->get_values();

    v_float& A = *tran_vals;
    v_double& b = *rhs;

    smart_ptr<bs_mesh_grdecl> sp_mesh(sp_mesh_iface, bs_static_cast());
    mesh_grdecl mesh = sp_mesh->get_wrapped();

    // important: ZYX order
    // direction dir along_dim1->X (along_dim2->Y, along_dim3->Z)

    // k = k1 //////////////////////////////////////////////////////////////////////////////////////////
    ext_ind[0] = k1 + j * Nz + i * Ny * Nz;
    mesh.calc_element (i, j, k1, element[0]);
    center[0] = element[0].get_center();
    element[0].get_plane (z_axis_minus, plane[0]);
    tz = mesh.calc_tran_boundary (ext_ind[0], plane[0], center[0], along_dim3);

    // FIXME
    b[0] = -tz*p_left;
    A[0] -= tz;

    // k1 < k < k2 /////////////////////////////////////////////////////////////////////////////////////
    for (k = k1; k < k2; ++k)
        {
            ext_ind[0] = k + j * Nz + i * Ny * Nz;
            mesh.calc_element (i, j, k, element[0]);
            center[0] = element[0].get_center();

            ext_ind[1] = ext_ind[0] + 1;
            mesh.calc_element (i, j, k + 1, element[1]);
            element[0].get_plane (z_axis_plus, plane[0]);

            //element[1].get_plane (z_axis_minus, plane[1]);

            center[1] = element[1].get_center();
            // don't pass plane[1] to calc_tran
            // because zcolumn cells are always fully adjacent
            //tz = mesh.calc_tran(ext_ind[0], ext_ind[1], plane[0], center[0], center[1], along_dim3, &plane[1]);
            tz = mesh.calc_tran(ext_ind[0], ext_ind[1], plane[0], center[0], center[1], along_dim3);

            n = k - k1;
            // Z-
            index = n + (n+1)*size;
            A[index] = tz;
            index += 1;
            A[index] -= tz;

            // Z+
            index = (n+1) + n*size;
            A[index] = tz;
            index -= 1;
            A[index] -= tz;
        }

    // k = k2 //////////////////////////////////////////////////////////////////////////////////////////
    ext_ind[0] = k2 + j * Nz + i * Ny * Nz;
    mesh.calc_element (i, j, k2, element[0]);
    center[0] = element[0].get_center();
    element[0].get_plane (z_axis_plus, plane[0]);
    tz = mesh.calc_tran_boundary (ext_ind[0], plane[0], center[0], along_dim3);

    b[size-1] = -tz*p_right;
    index  =  size*size - 1;
    A[index] -= tz;

    // solve system, find pressure
    solver->setup (tran);
    solver->solve (tran, rhs, p);

    dp = (p_left - (*p)[0]);
    dP = ( (p_left + (*p)[0])/2 - ((*p)[size-1] + p_right)/2 );

    mesh.calc_element (i, j, k1, element[0]);
    element[0].get_plane (z_axis_minus, plane[0]);
    center[1] = element[0].get_center ();
    get_plane_center (plane[0],  center[0]);

    dl = get_len(center[0], center[1])*2;

    mesh.calc_element (i, j, k2, element[1]);
    element[1].get_plane (z_axis_plus, plane[1]);
    get_plane_center (plane[1],  center[1]);

    dL = get_len(center[0], center[1]);

    if (dl < 10*EPSILON)
        {
            dl = 2*EPSILON;
            dL += EPSILON;
        }

    K = (dp/dl)/(dP/dL);

    return K;
}

t_double upsc::solve_pressure_block (t_int direction, t_long Ny, t_long Nz, t_long i1, t_long i2, t_long j1, t_long j2, t_long k1, t_long k2, spv_float ntg_, BS_SP(rs_mesh_iface) sp_mesh_iface)
{
    const t_double cdarcy = 0.008640;
    t_long size, i, j, k, ind[2], ext_ind[2], index, ny, nz;
    plane_t plane[2];
    mesh_element3d element[2];
    fpoint3d_t center[2];

    t_double tz, ty, tx;
    t_double p_left = 1, p_right = 0;
    t_double dL, dP, S, K, Q;

    spv_double p = BS_KERNEL.create_object(v_double::bs_type());
    spv_double rhs = BS_KERNEL.create_object(v_double::bs_type());
    spv_float tran_vals = BS_KERNEL.create_object(v_float::bs_type());
    sp_dens_mtx_t tran = BS_KERNEL.create_object("dens_matrix");
    sp_blu_solver_t solver = BS_KERNEL.create_object("blu_solver");

    size = (i2-i1)*(j2-j1)*(k2-k1);
    ny = j2 - j1;
    nz = k2 - k1;

    p->resize(size);
    rhs->resize(size);

    tran->init(size, size, 60);
    tran_vals = tran->get_values();

    v_float& A = *tran_vals;
    v_double& b = *rhs;
    v_float& ntg = *ntg_;

    smart_ptr<bs_mesh_grdecl> sp_mesh(sp_mesh_iface, bs_static_cast());
    mesh_grdecl mesh = sp_mesh->get_wrapped();

    // important: ZYX order

    // direction dir along_dim1->X (2->Y, 3->Z)

    for (i = i1; i < i2; ++i)
        for (j = j1; j < j2; ++j)
            for (k = k1; k < k2; ++k)
              {
                ext_ind[0] = k + j * Nz + i * Ny * Nz;
                ind[0] = (k-k1) + (j-j1)*nz + (i-i1)*ny*nz;
                mesh.calc_element (i, j, k, element[0]);
                center[0] = element[0].get_center();

                // TRANZ /////////////////////////////////////////////////////////////////////////////////////////////
                if (k!=k2-1)
                    {
                        ext_ind[1] = ext_ind[0] + 1;
                        ind[1] = ind[0] + 1;
                        mesh.calc_element (i, j, k + 1, element[1]);
                        element[0].get_plane (z_axis_plus, plane[0]);
                        element[1].get_plane (z_axis_minus, plane[1]);
                        center[1] = element[1].get_center();
                        tz = mesh.calc_tran(ext_ind[0], ext_ind[1], plane[0], center[0], center[1], along_dim3, &plane[1]);

                        // Z-
                        index = ind[0] + ind[1]*size;
                        A[index] = tz;
                        index += 1;
                        A[index] -= tz;

                        // Z+
                        index = ind[1] + ind[0]*size;
                        A[index] = tz;
                        index -= 1;
                        A[index] -= tz;
                    }

                // TRANY /////////////////////////////////////////////////////////////////////////////////////////////
                if (j != j2-1)
                    {
                        ext_ind[1] = ext_ind[0] + Nz;
                        ind[1] = ind[0] + nz;
                        mesh.calc_element (i, j + 1, k, element[1]);
                        element[0].get_plane (y_axis_plus, plane[0]);
                        element[1].get_plane (y_axis_minus, plane[1]);
                        center[1] = element[1].get_center();
                        ty = mesh.calc_tran(ext_ind[0], ext_ind[1], plane[0], center[0], center[1], along_dim2, &plane[1]);

                        // Y-
                        index = ind[0] + ind[1]*size;
                        A[index] = ty;
                        index += nz;
                        A[index] -= ty;

                        // Y+
                        index = ind[1] + ind[0]*size;
                        A[index] = ty;
                        index -= nz;
                        A[index] -= ty;
                    }

                // TRANX /////////////////////////////////////////////////////////////////////////////////////////////
                if (i != i2-1)
                    {
                        ext_ind[1] = ext_ind[0] + Ny * Nz;
                        ind[1] = ind[0] + ny*nz;
                        mesh.calc_element (i + 1, j, k, element[1]);
                        element[0].get_plane (x_axis_plus, plane[0]);
                        element[1].get_plane (x_axis_minus, plane[1]);
                        center[1] = element[1].get_center();
                        tx = mesh.calc_tran(ext_ind[0], ext_ind[1], plane[0], center[0], center[1], along_dim1, &plane[1]);

                        // X-
                        index = ind[0] + ind[1]*size;
                        A[index] = tx;
                        index += ny*nz;
                        A[index] -= tx;

                        // X+
                        index = ind[1] + ind[0]*size;
                        A[index] = tx;
                        index -= ny*nz;
                        A[index] -= tx;

                    }
               // Boundary Conditions /////////////////////////////////////////////////////////////////////////////////
               if (direction == 1)
                 {
                    if (i == i1)
                        {
                           element[0].get_plane (x_axis_minus, plane[0]);
                           tx = mesh.calc_tran_boundary (ext_ind[0], plane[0], center[0], along_dim1);
                           b[ind[0]] = -tx*p_left;
                           index  =  ind[0] + ind[0]*size;
                           A[index] -= tx;
                        }
                    if (i == i2-1)
                        {
                           element[0].get_plane (x_axis_plus, plane[0]);
                           tx = mesh.calc_tran_boundary (ext_ind[0], plane[0], center[0], along_dim1);
                           b[ind[0]] = -tx*p_right;
                           index  =  ind[0] + ind[0]*size;
                           A[index] -= tx;
                        }
                 }
               else if (direction == 2)
                 {
                    if (j == j1)
                        {
                           element[0].get_plane (y_axis_minus, plane[0]);
                           ty = mesh.calc_tran_boundary (ext_ind[0], plane[0], center[0], along_dim2);
                           b[ind[0]] = -ty*p_left;
                           index  =  ind[0] + ind[0]*size;
                           A[index] -= ty;
                        }
                    if (j == j2-1)
                        {
                           element[0].get_plane (y_axis_plus, plane[0]);
                           ty = mesh.calc_tran_boundary (ext_ind[0], plane[0], center[0], along_dim2);
                           b[ind[0]] = -ty*p_right;
                           index  =  ind[0] + ind[0]*size;
                           A[index] -= ty;
                        }
                 }
               else if (direction == 3)
                 {
                    if (k == k1)
                        {
                           element[0].get_plane (z_axis_minus, plane[0]);
                           tz = mesh.calc_tran_boundary (ext_ind[0], plane[0], center[0], along_dim3);
                           b[ind[0]] = -tz*p_left;
                           index  =  ind[0] + ind[0]*size;
                           A[index] -= tz;
                        }
                    if (k == k2-1)
                        {
                           element[0].get_plane (z_axis_plus, plane[0]);
                           tz = mesh.calc_tran_boundary (ext_ind[0], plane[0], center[0], along_dim3);
                           b[ind[0]] = -tz*p_right;
                           index  =  ind[0] + ind[0]*size;
                           A[index] -= tz;
                        }
                 }

              }

    solver->setup (tran);
    solver->solve (tran, rhs, p);

    // Q=sum(q) q=T*dp
    // Q=k*S*dP/dL
    // for permx and permy don't forget NTG!

    Q = 0;
    S = 0;

    if (direction == 1)
      {
        i = i1;
        for (j = j1; j < j2; ++j)
            for (k = k1; k < k2; ++k)
              {
                mesh.calc_element (i, j, k, element[0]);
                element[0].get_plane (x_axis_minus, plane[0]);
                center[0] = element[0].get_center ();
                get_plane_center (plane[0],  center[1]);

                index = k + j * Nz + i * Ny * Nz;
                S += ntg[index]*find_area_of_side (plane[0][0], plane[0][1], plane[0][2], plane[0][3]);
                tx = mesh.calc_tran_boundary (index, plane[0], center[0], along_dim1);

                index = (k-k1) + (j-j1)*nz + (i-i1)*ny*nz;
                dP = (p_left - (*p)[index]);
                Q += tx*dP;
              }
      }
    else if (direction == 2)
      {
        j = j1;
        for (i = i1; i < i2; ++i)
            for (k = k1; k < k2; ++k)
              {
                mesh.calc_element (i, j, k, element[0]);
                element[0].get_plane (y_axis_minus, plane[0]);
                center[0] = element[0].get_center ();
                get_plane_center (plane[0],  center[1]);

                index = k + j * Nz + i * Ny * Nz;
                S += ntg[index]*find_area_of_side (plane[0][0], plane[0][1], plane[0][2], plane[0][3]);
                ty = mesh.calc_tran_boundary (index, plane[0], center[0], along_dim2);

                index = (k-k1) + (j-j1)*nz + (i-i1)*ny*nz;
                dP = (p_left - (*p)[index]);
                Q += ty*dP;
              }
      }
    else if (direction == 3)
      {
        k = k1;
        for (i = i1; i < i2; ++i)
            for (j = j1; j < j2; ++j)
              {
                mesh.calc_element (i, j, k, element[0]);
                element[0].get_plane (z_axis_minus, plane[0]);
                center[0] = element[0].get_center ();
                get_plane_center (plane[0],  center[1]);

                index = k + j * Nz + i * Ny * Nz;
                S += find_area_of_side (plane[0][0], plane[0][1], plane[0][2], plane[0][3]);
                tz = mesh.calc_tran_boundary (index, plane[0], center[0], along_dim3);

                index = (k-k1) + (j-j1)*nz + (i-i1)*ny*nz;
                dP = (p_left - (*p)[index]);
                Q += tz*dP;
              }
      }

    i = i1; j = j1; k = k1;
    if (direction == 1)
      {
        mesh.calc_element (i, j, k, element[0]);
        element[0].get_plane (x_axis_minus, plane[0]);
        get_plane_center (plane[0],  center[0]);

        index = (k-k1) + (j-j1)*nz + (i-i1)*ny*nz;
        dP = (p_left + (*p)[index])/2;

        i = i2-1;
        mesh.calc_element (i, j, k, element[1]);
        element[1].get_plane (x_axis_plus, plane[1]);
        get_plane_center (plane[1],  center[1]);

        index = (k-k1) + (j-j1)*nz + (i-i1)*ny*nz;
        dP -= ((*p)[index] + p_right)/2;
      }
    else if (direction == 2)
      {
        mesh.calc_element (i, j, k, element[0]);
        element[0].get_plane (y_axis_minus, plane[0]);
        get_plane_center (plane[0],  center[0]);

        index = (k-k1) + (j-j1)*nz + (i-i1)*ny*nz;
        dP = (p_left + (*p)[index])/2;

        j = j2-1;
        mesh.calc_element (i, j, k, element[1]);
        element[1].get_plane (y_axis_plus, plane[1]);
        get_plane_center (plane[1],  center[1]);

        index = (k-k1) + (j-j1)*nz + (i-i1)*ny*nz;
        dP -= ((*p)[index] + p_right)/2;
      }
    else if (direction == 3)
      {
        mesh.calc_element (i, j, k, element[0]);
        element[0].get_plane (z_axis_minus, plane[0]);
        get_plane_center (plane[0],  center[0]);

        index = (k-k1) + (j-j1)*nz + (i-i1)*ny*nz;
        dP = (p_left + (*p)[index])/2;

        k = k2-1;
        mesh.calc_element (i, j, k, element[1]);
        element[1].get_plane (z_axis_plus, plane[1]);
        get_plane_center (plane[1],  center[1]);

        index = (k-k1) + (j-j1)*nz + (i-i1)*ny*nz;
        dP -= ((*p)[index] + p_right)/2;
      }

    dL = get_len(center[0], center[1]);
    if (S < EPSILON || dP < EPSILON)
        K = 0;
    else
        K = Q*dL/(cdarcy*S*dP);

    return K;

}

spv_float upsc::upscale_permz_zcolumn (t_long Nx, t_long Ny, t_long Nz, spv_uint layers_, spv_float permz_, spv_uint actnum_, BS_SP(rs_mesh_iface) sp_mesh_iface)
  {
    t_int i, j, k, n, Nz_upsc, k1, k2, z1, ind, index;
    t_long layer_size, new_cube_size;

    t_double upsc_factor;

    spv_float new_permz = BS_KERNEL.create_object(v_float::bs_type());
    v_float& permz = *permz_;
    v_uint& actnum = *actnum_;
    v_uint& layers = *layers_;

    smart_ptr<bs_mesh_grdecl> sp_mesh(sp_mesh_iface, bs_static_cast());
    mesh_grdecl mesh = sp_mesh->get_wrapped();

    Nz_upsc = layers.size();
    layer_size = Nx*Ny;

    // important: XYZ order
    // index <-- (i, j, k)
    // index = i + j*Nx + k*Nx*Ny;

    for (n = 0; n < Nz_upsc; ++n)
        {
            k1 = layers[n];

            if (n == Nz_upsc-1)
                k2 = Nz - 1;
            else
                k2 = layers[n+1] - 1;

            if (k1 != k2)
                {
                    z1 = k1*layer_size;
                    for (j = 0; j < Ny; ++j)
                        for (i = 0; i < Nx; ++i)
                            {
                                index = i + j * Nx + z1;

                                // in general case call function that finds isolated bodies
                                for (k = k1; k <= k2; k++)
                                    {
                                        ind = i + j * Nx + k * Nx * Ny;
                                        if (!actnum [ind])
                                            {
                                                permz[index] = 0;
                                                break;
                                            }
                                    }
                                if (permz[index])
                                    {
                                        upsc_factor = solve_pressure_zcolumn (Ny, Nz, i, j, k1, k2, sp_mesh_iface);
                                        permz[index] *= upsc_factor;
                                    }

                            }
                }
        }

    new_cube_size = Nz_upsc*layer_size;
    new_permz->resize(new_cube_size);

    i = 0;
    for ( n = 0; n < Nz_upsc; n++)
        {
            k = layers[n];
            std::copy ( &permz[k*layer_size], &permz[(k+1)*layer_size], &(*new_permz)[i*layer_size] );
            i++;
        }

    return new_permz;
  }

spv_float upsc::upscale_perm_block (t_int dir, t_long Nx, t_long Ny, t_long ux, t_long uy, t_long Nz, spv_uint layers_, spv_float ntg_, spv_uint actnum_, BS_SP(rs_mesh_iface) sp_mesh_iface)
  {
    t_int i, j, n, i1, i2, j1, j2, k1, k2, new_k, new_ind;
    t_long new_Nx, new_Ny, new_Nz;
    t_long new_layer_size, new_cube_size;

    spv_float new_perm = BS_KERNEL.create_object(v_float::bs_type());
    v_uint& actnum = *actnum_;
    v_uint& layers = *layers_;

    smart_ptr<bs_mesh_grdecl> sp_mesh(sp_mesh_iface, bs_static_cast());
    mesh_grdecl mesh = sp_mesh->get_wrapped();

    t_int rx, ry;
    rx = Nx % ux;
    ry = Ny % uy;
    new_Nx = ceil(double(Nx)/double(ux));
    new_Ny = ceil(double(Ny)/double(uy));
    new_Nz = layers.size();
    new_layer_size = new_Nx * new_Ny;
    new_cube_size = new_Nx*new_Ny*new_Nz;
    new_perm->resize(new_cube_size);

    // important: XYZ order
    // index <-- (i, j, k)
    // index = i + j*Nx + k*Nx*Ny;

    // FIXME: call function that finds isolated bodies

    new_k = 0;
    for (n = 0; n < new_Nz; ++n)
      {
        k1 = layers[n];

        if (n == new_Nz-1)
            k2 = Nz;
        else
            k2 = layers[n+1] - 1;

        for (j = 0; j < new_Ny-1; ++j)
          {
            for (i = 0; i < new_Nx-1; ++i)
              {
                i1 = ux*i;
                i2 = i1 + ux;
                j1 = uy*j;
                j2 = j1 + uy;
                new_ind = i + j*new_Nx + new_k*new_layer_size;
                (*new_perm)[new_ind] = solve_pressure_block (dir, Ny, Nz, i1, i2, j1, j2, k1, k2, ntg_, sp_mesh_iface);
              }

            if (rx)
              {
                i1 = ux*(new_Nx-1);
                i2 = i1 + rx;
                j1 = uy*j;
                j2 = j1 + uy;
                new_ind = new_Nx-1 + j*new_Nx + new_k*new_layer_size;
                (*new_perm)[new_ind] = solve_pressure_block (dir, Ny, Nz, i1, i2, j1, j2, k1, k2, ntg_, sp_mesh_iface);
              }
          }

        if (ry)
          {
            for (i = 0; i < new_Nx-1; ++i)
              {
                i1 = ux*i;
                i2 = i1 + ux;
                j1 = uy*(new_Ny-1);
                j2 = j1 + ry;
                new_ind = i + (new_Ny-1)*new_Nx + new_k*new_layer_size;
                (*new_perm)[new_ind] = solve_pressure_block (dir, Ny, Nz, i1, i2, j1, j2, k1, k2, ntg_, sp_mesh_iface);

              }

            if (rx)
              {
                i1 = ux*(new_Nx-1);
                i2 = i1 + rx;
                j1 = uy*(new_Ny-1);
                j2 = j1 + ry;
                new_ind = new_Nx-1 + (new_Ny-1)*new_Nx + new_k*new_layer_size;
                (*new_perm)[new_ind] = solve_pressure_block (dir, Ny, Nz, i1, i2, j1, j2, k1, k2, ntg_, sp_mesh_iface);
              }
          }

        new_k ++;
      }

    return new_perm;
  }

  bp::tuple
  upsc::king_method (t_long Nx, t_long Ny, t_long Nz, t_long Nz_upsc,
                            spv_float vol_, spv_float ntg_, spv_float poro_, spv_float permx_)
  {
    t_int i, k, n, k0, k1, k2, k3;
    t_long layer_size, new_cube_size;
    t_float sum_dW;

    v_float& vol = *vol_;
    v_float& ntg = *ntg_;
    v_float& poro = *poro_;
    v_float& permx = *permx_;

    std::list <t_int> layers;
    std::list <t_int>::iterator lit, tmp_it;

    layer_mmap_t sum_dW2layer;
    layer_mmap_it_t mit;
    layer_map_t layer2mmap_it;
    layer_map_it_t lmit;

    spv_float new_vol = BS_KERNEL.create_object(v_float::bs_type());
    spv_float new_ntg = BS_KERNEL.create_object(v_float::bs_type());
    spv_float new_poro = BS_KERNEL.create_object(v_float::bs_type());
    spv_float new_permx = BS_KERNEL.create_object(v_float::bs_type());
    spv_uint layers_v = BS_KERNEL.create_object(v_uint::bs_type());

    layer_size = Nx*Ny;

    for (i = 0;i < Nz; i++)
        layers.push_back(i);

    // important: XYZ order
    // index <-- (i, j, k)
    // index = i + j*Nx + k*Nx*Ny;

    // calculate sum_dW for every pair of adjacent layers
    // and store it in sumdw2layer multimap (sum_dW -> k) sorted in increasing order
    // and in layer2mmapit map (k -> mmap_it)
    for (k = 0; k < Nz-1; k++)
    {
        sum_dW = calc_sum_dW ( k, k+1,  Nx, Ny, vol_, ntg_, poro_, permx_ );
        mit = sum_dW2layer.insert(std::make_pair(sum_dW, k));
        layer2mmap_it[k] = mit;
    }

    k0 = 0;
    k3 = Nz-1;

    for (n=Nz;n>Nz_upsc;n--)
    {
        // [k0, k1, k2, k3] - indices of 4 consequentive layers
        // [k1, k2]  - indices of 2 layers to be united
        k1 = (*sum_dW2layer.begin()).second;
        lit = find(layers.begin(), layers.end(), k1);
        tmp_it = lit;
        tmp_it ++;
        k2 = (*tmp_it);

        // upscaling of cubes
        upscale_cubes (k1, k2, Nx, Ny, vol_, ntg_, poro_, permx_ );

        // erase sum_dW for (k1; k2) pair of layers
        mit = layer2mmap_it[k1];
        sum_dW2layer.erase(mit);
        layer2mmap_it.erase(k1);

        // if k2 isn't the last layer
        if (k2 != layers.back())
            {
                // erase sum_dW for (k2; k3) pair of layers
                mit = layer2mmap_it[k2];
                sum_dW2layer.erase(mit);
                layer2mmap_it.erase(k2);

                tmp_it ++;
                k3 = (*tmp_it);
                tmp_it --;

                // add sum_dW for (k1; k3) pair of layers
                sum_dW = calc_sum_dW ( k1, k3, Nx, Ny, vol_, ntg_, poro_, permx_ );
                mit = sum_dW2layer.insert ( std::make_pair(sum_dW, k1) );
                layer2mmap_it[k1] = mit;
            }

        // delete k2 layer
        layers.erase(tmp_it);

        // if k1 isn't the first layer
        if (k1 != 0)
            {
                tmp_it = lit;
                tmp_it --;
                k0 = (*tmp_it);

                // erase sum_dW for (k0; k1) pair of layers
                mit = layer2mmap_it[k0];
                sum_dW2layer.erase(mit);
                layer2mmap_it.erase(k0);

                // add sum_dW for (k0; k1) pair of layers
                sum_dW = calc_sum_dW ( k0, k1, Nx, Ny, vol_, ntg_, poro_, permx_ );
                mit = sum_dW2layer.insert ( std::make_pair(sum_dW, k0) );
                layer2mmap_it[k0] = mit;
            }

    }

    new_cube_size = Nz_upsc*layer_size;

    new_vol->resize(new_cube_size);
    new_ntg->resize(new_cube_size);
    new_poro->resize(new_cube_size);
    new_permx->resize(new_cube_size);
    layers_v->resize(Nz_upsc);

    i = 0;
    for (lit=layers.begin();lit!=layers.end();lit++)
        {
            k = (*lit);
            std::copy ( &vol[k*layer_size], &vol[(k+1)*layer_size], &(*new_vol)[i*layer_size] );
            std::copy ( &ntg[k*layer_size], &ntg[(k+1)*layer_size], &(*new_ntg)[i*layer_size] );
            std::copy ( &poro[k*layer_size], &poro[(k+1)*layer_size], &(*new_poro)[i*layer_size] );
            std::copy ( &permx[k*layer_size], &permx[(k+1)*layer_size], &(*new_permx)[i*layer_size] );
            i++;
        }

    std::copy ( layers.begin(), layers.end(), &(*layers_v)[0] );

    return bp::make_tuple (layers_v, new_vol, new_ntg, new_poro, new_permx) ;
  }

#ifdef BSPY_EXPORTING_PLUGIN
  std::string
  upsc::py_str () const
    {
      std::stringstream s;
      s << wstr2str (sp_prop->py_str ()) << "\n";
      return s.str ();
    }
#endif //BSPY_EXPORTING_PLUGIN
/////////////////////////////////BS Register
/////////////////////////////////Stuff//////////////////////////

  BLUE_SKY_TYPE_STD_CREATE (upsc);
  BLUE_SKY_TYPE_STD_COPY (upsc);

  BLUE_SKY_TYPE_IMPL(upsc,  upsc_iface, "upsc", "upscaling", "hdm upscaling");

}  // blue_sky namespace


/* Copyright 2019 The Spin-Scenario Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
    http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/

#include "spin_system.h"

#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/format.hpp>

namespace ssl {
namespace spinsys {
spin_system::spin_system(const std::string &filename) : inter_(comp_) {

  sol::state lua;
  lua.script_file(filename);
  sol::table t = lua["sys"];
  set_sys(t);
}
spin_system::spin_system(const sol::table &t) : inter_(comp_) {
  set_sys(t);
}
spin_system::~spin_system() {
}
void spin_system::set_sys(const sol::table &t) {
  if (!t.valid() || t.empty())
    throw std::runtime_error("invalid 'spin_system' table parameters (nil or empty).");

  //try {
//  std::string str = retrieve_table("B0", t).as<std::string>();
//  boost::to_lower(str);
//  std::vector<std::string> str_vec;
//  boost::split(str_vec, str, boost::is_any_of(", "), boost::token_compress_on);
//  double val_B0 = boost::lexical_cast<double>(str_vec[0]);
//  if (str_vec[1] == "tesla" || str_vec[1] == "t")
//    set_magnet_field(val_B0);
//  if (str_vec[1] == "mhz")
//    set_proton_freq(val_B0);

  if (g_B0_ < 0) {
    throw std::runtime_error("static magnetic field not set yet!");
  }

  comp_.B0_ = g_B0_;

  set_isotopes(retrieve_table("spin", t).as<std::string>());
  //setBasis();
  inter_.alloc();

  if (is_retrievable("zeeman", t))
    inter_.set_zeeman(retrieve_table("zeeman", t).as<std::string>());

  if (is_retrievable("jcoupling", t))
    inter_.set_Jcoupling(retrieve_table("jcoupling", t).as<std::string>());

  if (is_retrievable("coord", t))
    inter_.set_Jcoupling_coords(retrieve_table("coord", t).as<sol::table>());

  if (is_retrievable("relaxation", t))
    inter_.set_relaxation(retrieve_table("relaxation", t).as<std::string>());

  inter_.init();
  inter_.set_assumption("nmr");
  /*} catch (const std::runtime_error& e) {
      std::string s = str(boost::format("%s\n") % std::string(e.what()));
      ssl_color_text("err", s);
  }*/
}

void spin_system::set_isotopes(const std::string list) {
  std::vector<std::string> symbol_vec;
  boost::split(symbol_vec, list, boost::is_any_of("\t, "), boost::token_compress_on);
  for (size_t i = 0; i < symbol_vec.size(); i++)
    comp_.add_spin(isotope(symbol_vec[i]));
#ifdef SSL_OUTPUT_ENABLE
  std::string s = str(boost::format("%s %s.\n") % "isotopes set to be" % list);
  ssl_color_text("info", s);
#endif
  comp_.init();
}

void spin_system::set_magnet_field(double tesla) {
  comp_.B0_ = tesla;
#ifdef SSL_OUTPUT_ENABLE
  std::string s = str(boost::format("%s %.3f Tesla.\n") % "magnet field set to be" % tesla);
  ssl_color_text("info", s);
#endif
}

void spin_system::set_proton_freq(double MHz) {
  isotope proton("1H");
  double tesla = MHz * 2 * _pi / proton.gamma() * 1e6;
#ifdef SSL_OUTPUT_ENABLE
  std::string s = str(boost::format("%s %.3f MHz.\n") % "proton resonance frequency set to be" % MHz);
  ssl_color_text("info", s);
#endif
  set_magnet_field(tesla);
}
double spin_system::get_proton_freq() const {
  return comp_.get_proton_freq();
}
sp_mat spin_system::op(const std::string &list) const {
  return op(list, kComm);
}
sp_mat spin_system::op(const sol::table &t) const {
  return op(t, kComm);
}
sp_mat spin_system::op(const std::string &list, op_side type) const {
  return comp_.op(list, type);
}
sp_mat spin_system::op(const sol::table &t, op_side type) const {
  if (!t.valid() || t.empty())
    throw std::runtime_error("invalid 'op' table parameters (nil or empty).");

  std::vector<std::string> s;
  for (size_t i = 0; i < t.size(); i++) {
    sol::object val = t[i + 1];
    std::string item;
    switch (val.get_type()) {
      case sol::type::number:item = boost::lexical_cast<std::string>(val.as<int>());
        break;
      case sol::type::string:item = val.as<std::string>();
        break;
      default:break;
    }
    s.push_back(item);
  }
  std::string list = boost::algorithm::join(s, " ");
  return comp_.op(list, type);
}

sp_cx_vec spin_system::equilibrium_state() const {
  return inter_.equilibrium_state();
  }

sp_cx_mat spin_system::smart_op(const std::string expr) const {
  state_par s = state_evaluate(expr);
  int num = s.coeff.size();
  sp_cx_mat result = s.coeff[0] * op(s.expr[0]);
  for (int i = 1; i < num; i++)
    result += s.coeff[i] * op(s.expr[i]);
  return result;
}

sp_cx_vec spin_system::smart_state(const std::string expr) const {
  state_par s = state_evaluate(expr);
  int num = s.coeff.size();
  sp_cx_vec result = s.coeff[0] * state(s.expr[0]);
  for (int i = 1; i < num; i++)
    result += s.coeff[i] * state(s.expr[i]);
  return result;
}

std::map<std::string, sp_cx_vec> spin_system::cartesian_basis_states() const {
  std::string base_expr = "(1+Ix+Iy+Iz)";
  int nspins = comp_.nspins();
  std::string expr_in;
  boost::regex reg("I(\\w)");
  std::vector<std::string> exprs(nspins);
  for (int i = 0; i < nspins; i++) {
    exprs[i] = base_expr;
    std::string id = boost::lexical_cast<std::string>(i + 1);
    std::string rep = "I" + id + "$1";
    std::string s = boost::regex_replace(exprs[i], reg, rep);
    exprs[i] = s;
    //std::cout << rep<<" "<<exprs[i] << "\n";
  }

  expr_in = exprs[0];
  for (int i = 1; i < nspins; i++)
    expr_in += "*" + exprs[i];

  expr_in = "Simplify(" + expr_in + ")";
  //std::cout << expr_in<<"\n";
  std::string expr_out = yacas_evaluate(expr_in);
  boost::erase_all(expr_out, ";");
  //std::cout << expr_out << "\n";
  //boost::replace_all(expr_out, "J", "I");
  //std::vector<std::string> expr_vec;
  std::vector<std::string> expr;
  boost::algorithm::split(expr, expr_out, boost::is_any_of("+"));
  expr.pop_back(); // !!! remove 1.
  std::map<std::string, sp_cx_vec> basis;
  for (size_t i = 0; i < expr.size(); i++) {
    boost::erase_all(expr[i], "*");
    //std::cout << i + 1 << " " << expr[i] << "\n";
    basis.insert(std::pair<std::string, sp_cx_vec>(expr[i], smart_state(expr[i])));
  }
  /*std::map<std::string, sp_cx_vec>::iterator iter;
  for (iter = basis.begin(); iter != basis.end(); iter++) {
      std::string label = iter->first;
      sp_cx_vec val = iter->second;
      std::cout << label << "\n" << val << "\n";
  }*/
  return basis;
}

sp_cx_vec spin_system::state(const std::string &list) const {
  return comp_.state(list);
}
sp_cx_vec spin_system::state(const sol::table &t) const {
  if (!t.valid() || t.empty())
    throw std::runtime_error("invalid 'op' table parameters (nil or empty).");
  std::vector<std::string> s;
  for (size_t i = 0; i < t.size(); i++) {
    sol::object val = t[i + 1];
    std::string item;
    switch (val.get_type()) {
      case sol::type::number:item = boost::lexical_cast<std::string>(val.as<int>());
        break;
      case sol::type::string:item = val.as<std::string>();
        break;
      default:break;
    }
    s.push_back(item);
  }
  std::string list = boost::algorithm::join(s, " ");
  return comp_.state(list);
}

ham_op spin_system::hamiltonian(const op_side type, bool build_aniso) const {
  return inter_.hamiltonian(type, build_aniso);
}

sp_cx_mat spin_system::free_hamiltonian() const {
  return hamiltonian(kComm, 0).isotropic;
}

std::vector<sp_cx_mat> spin_system::free_hamiltonians() {
  std::vector<sp_cx_mat> L0s;
  std::vector<std::vector<cs_par>> result_cs = inter_.parsing_zeeman_broadband();
  std::vector<std::vector<jcoup_par>> result_jcoup = inter_.parsing_Jcoupling_broadband();
  if (result_cs.size())
    std::cout << "chemical shifts num: " << result_cs.size() << "\n";
  if (result_jcoup.size())
    std::cout << "j-coupling num: " << result_jcoup.size() << "\n";
  // zeeman case.
  std::vector<double> scalars = inter_.zeeman_.scalars; // backup.
  mat scalar = inter_.coupling_.scalar;
  if (result_cs.size()&&result_jcoup.size()==0)
  for (size_t i = 0; i < result_cs.size(); i++) {
    for (size_t j = 0; j < result_cs[i].size(); j++) {
      const cs_par &cur = result_cs[i][j];
      //std::cout << (cur.id + 1) << " " << inter_.zeeman_.scalars[cur.id] << " ";
      inter_.zeeman_.scalars[cur.id] += cur.val;
      //std::cout << (cur.id + 1) << " " << inter_.zeeman_.scalars[cur.id] << " ";
    }
    //std::cout << "\n";
    inter_.init_broadband();
    L0s.push_back(free_hamiltonian());
    inter_.zeeman_.scalars = scalars; // recover to original pars for the next L0 calculation.
  }
  // J-coupling case.
    if (result_cs.size()==0&&result_jcoup.size())
  for (size_t i = 0; i < result_jcoup.size(); i++) {
    for (size_t j = 0; j < result_jcoup[i].size(); j++) {
      const jcoup_par &cur = result_jcoup[i][j];
      int id1 = cur.id1;
      int id2 = cur.id2;
      inter_.coupling_.scalar(id1, id2) += cur.val;
      if (id1 != id2)
        inter_.coupling_.scalar(id2, id1) += cur.val;
      //std::cout << (cur.id1 + 1) << " " << (cur.id2 + 1) << " " << inter_.coupling_.scalar(id1, id2) << " ";
    }
    //std::cout << "\n";
    inter_.init_broadband();
    L0s.push_back(free_hamiltonian());
    inter_.coupling_.scalar = scalar;
  }
	//omp_set_num_threads(omp_core_num);
	if(result_cs.size()&&result_jcoup.size()) {
    //#pragma omp parallel for
    for (size_t i = 0; i < result_cs.size(); i++) {
      if (i % 100 == 0) std::cout << i << "==>\n";

      for (size_t j = 0; j < result_cs[i].size(); j++) {
        const cs_par &cur = result_cs[i][j];
        inter_.zeeman_.scalars[cur.id] += cur.val;
      }

      for (size_t ii = 0; ii < result_jcoup.size(); ii++) {

        for (size_t jj = 0; jj < result_jcoup[ii].size(); jj++) {
          const jcoup_par &cur = result_jcoup[ii][jj];
          int id1 = cur.id1;
          int id2 = cur.id2;
          inter_.coupling_.scalar(id1, id2) += cur.val;
          if (id1 != id2) inter_.coupling_.scalar(id2, id1) += cur.val;
        }

        inter_.init_broadband();
        L0s.push_back(free_hamiltonian());

        inter_.coupling_.scalar = scalar;
      }
      inter_.zeeman_.scalars = scalars;  // recover to original pars for the next L0 calculation.
    }
	}
  return L0s;
}

sol::object spin_system::free_hamiltonians(sol::this_state s) {
  sol::state_view lua(s);
  sol::table t = lua.create_table();
  std::vector<sp_cx_mat> L0s = free_hamiltonians();
  for (size_t i = 0; i < L0s.size(); i++)
    t.add(L0s[i]);
  return t;
}
sp_cx_mat spin_system::relaxation() const {
  return inter_.relaxation();
}

sp_cx_mat spin_system::total_hamiltonian() const {
  return free_hamiltonian() + ci * relaxation();
}
rf_ham spin_system::rf_hamiltonian() const {
  rf_ham rf_ctrl;
  rf_ctrl.init(channels());
  for (size_t i = 0; i < rf_ctrl.channels; i++) {
    // Get the control operators
    std::string list_plus = rf_ctrl.chs[i] + " I+";
    std::string list_minus = rf_ctrl.chs[i] + " I-";
    sp_mat Lp = op(list_plus);
    sp_mat Lm = op(list_minus);
    rf_ctrl.Lx[i] = 0.5 * (Lp + Lm).cast<cd>();  // cx
    rf_ctrl.Ly[i] = -0.5 * ci * (Lp - Lm).cast<cd>();  // cy
  }
  return rf_ctrl;
}
vec spin_system::nominal_broadband() {
  if (inter_.zeeman_.bb_scalars.size())
    return inter_.zeeman_.bb_scalars[0].nominal_offset;
  if (inter_.coupling_.bb_scalars.size())
    return inter_.coupling_.bb_scalars[0].nominal_offset;
  return vec(1);
}

}
}


//==============================================================================
//         Copyright 2003 - 2011   LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011   LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef NT2_TRIGONOMETRIC_CONSTANTS_PIO2_2_HPP_INCLUDED
#define NT2_TRIGONOMETRIC_CONSTANTS_PIO2_2_HPP_INCLUDED

#include <nt2/include/functor.hpp>
#include <boost/simd/constant/hierarchy.hpp>
#include <boost/simd/constant/register.hpp>
namespace nt2
{
  namespace tag
  {
   /*!
     @brief Pio2_2 generic tag

     Represents the Pio2_2 constant in generic contexts.

     @par Models:
        Hierarchy
   **/
    //6.07710050630396597660e-11
    BOOST_SIMD_CONSTANT_REGISTER( Pio2_2, double
                                , 0, 0x37354400
                                , 0x3DD0B4611A600000ll
                                )
  }
  namespace ext
  {
   template<class Site, class... Ts>
   BOOST_FORCEINLINE generic_dispatcher<tag::Pio2_2, Site> dispatching_Pio2_2(adl_helper, boost::dispatch::meta::unknown_<Site>, boost::dispatch::meta::unknown_<Ts>...)
   {
     return generic_dispatcher<tag::Pio2_2, Site>();
   }
   template<class... Args>
   struct impl_Pio2_2;
  }
  /*!
    Constant used in modular computation involving \f$\pi\f$

    @par Semantic:

    For type T0:

    @code
    T0 r = Pio2_2<T0>();
    @endcode

    @return a value of type T0
  **/
  BOOST_SIMD_CONSTANT_IMPLEMENTATION(tag::Pio2_2, Pio2_2);
}

#endif


/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

/*
 Copyright (C) 2008 Andreas Gaida
 Copyright (C) 2008, 2009 Ralph Schreyer
 Copyright (C) 2008, 2009 Klaus Spanderen

 This file is part of QuantLib, a free-software/open-source library
 for financial quantitative analysts and developers - http://quantlib.org/

 QuantLib is free software: you can redistribute it and/or modify it
 under the terms of the QuantLib license.  You should have received a
 copy of the license along with this program; if not, please email
 <quantlib-dev@lists.sf.net>. The license is also available online at
 <http://quantlib.org/license.shtml>.

 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 license for more details.
*/

#include <ql/pricingengines/barrier/fdhestonbarrierengine.hpp>
#include <ql/methods/finitedifferences/stepconditions/fdmstepconditioncomposite.hpp>
#include <ql/methods/finitedifferences/utilities/fdmdividendhandler.hpp>
#include <ql/methods/finitedifferences/meshers/fdmhestonvariancemesher.hpp>
#include <ql/methods/finitedifferences/utilities/fdmdirichletboundary.hpp>
#include <ql/methods/finitedifferences/utilities/fdminnervaluecalculator.hpp>
#include <ql/methods/finitedifferences/operators/fdmlinearoplayout.hpp>
#include <ql/methods/finitedifferences/meshers/fdmmeshercomposite.hpp>
#include <ql/methods/finitedifferences/meshers/fdmblackscholesmesher.hpp>
#include <ql/pricingengines/barrier/fdhestonrebateengine.hpp>
#include <ql/pricingengines/vanilla/fdhestonvanillaengine.hpp>
#include <boost/make_shared.hpp>

namespace QuantLib {

    FdHestonBarrierEngine::FdHestonBarrierEngine(
            const boost::shared_ptr<HestonModel>& model,
            Size tGrid, Size xGrid, Size vGrid, Size dampingSteps,
            const FdmSchemeDesc& schemeDesc,
            const boost::shared_ptr<LocalVolTermStructure>& leverageFct)
    : GenericModelEngine<HestonModel,
                        DividendBarrierOption::arguments,
                        DividendBarrierOption::results>(model),
      tGrid_(tGrid), xGrid_(xGrid),
      vGrid_(vGrid), dampingSteps_(dampingSteps),
      schemeDesc_(schemeDesc),
      leverageFct_(leverageFct) {
    }

    void FdHestonBarrierEngine::calculate() const {

        // 1. Mesher
        const boost::shared_ptr<HestonProcess>& process = model_->process();
        const Time maturity = process->time(arguments_.exercise->lastDate());

        // 1.1 The variance mesher
        const Size tGridMin = 5;
        const boost::shared_ptr<FdmHestonVarianceMesher> varianceMesher(
			boost::make_shared<FdmHestonVarianceMesher>(vGrid_, process, maturity,
                                        std::max(tGridMin, tGrid_/50)));

        // 1.2 The equity mesher
        const boost::shared_ptr<StrikedTypePayoff> payoff =
            boost::dynamic_pointer_cast<StrikedTypePayoff>(arguments_.payoff);

        Real xMin=Null<Real>();
        Real xMax=Null<Real>();
        if (   arguments_.barrierType == Barrier::DownIn
            || arguments_.barrierType == Barrier::DownOut) {
            xMin = std::log(arguments_.barrier);
        }
        if (   arguments_.barrierType == Barrier::UpIn
            || arguments_.barrierType == Barrier::UpOut) {
            xMax = std::log(arguments_.barrier);
        }

        const boost::shared_ptr<Fdm1dMesher> equityMesher(
            new FdmBlackScholesMesher(
                xGrid_,
                FdmBlackScholesMesher::processHelper(
                    process->s0(), process->dividendYield(),
                    process->riskFreeRate(), varianceMesher->volaEstimate()),
                maturity, payoff->strike(),
                xMin, xMax, 0.0001, 1.5,
                std::make_pair(Null<Real>(), Null<Real>()),
                arguments_.cashFlow));

        const boost::shared_ptr<FdmMesher> mesher (
			boost::make_shared<FdmMesherComposite>(equityMesher, varianceMesher));

        // 2. Calculator
        boost::shared_ptr<FdmInnerValueCalculator> calculator(
			boost::make_shared<FdmLogInnerValue>(payoff, mesher, 0));

        // 3. Step conditions
        std::list<boost::shared_ptr<StepCondition<Array> > > stepConditions;
        std::list<std::vector<Time> > stoppingTimes;

        // 3.1 Step condition if discrete dividends
        boost::shared_ptr<FdmDividendHandler> dividendCondition(
			boost::make_shared<FdmDividendHandler>(arguments_.cashFlow, mesher,
                                   process->riskFreeRate()->referenceDate(),
                                   process->riskFreeRate()->dayCounter(), 0));

        if(!arguments_.cashFlow.empty()) {
            stepConditions.push_back(dividendCondition);
            stoppingTimes.push_back(dividendCondition->dividendTimes());
        }

        QL_REQUIRE(arguments_.exercise->type() == Exercise::European,
                   "only european style option are supported");

        boost::shared_ptr<FdmStepConditionComposite> conditions(
			boost::make_shared<FdmStepConditionComposite>(stoppingTimes, stepConditions));

        // 4. Boundary conditions
        FdmBoundaryConditionSet boundaries;
        if (   arguments_.barrierType == Barrier::DownIn
            || arguments_.barrierType == Barrier::DownOut) {
            boundaries.push_back(
				boost::make_shared<FdmDirichletBoundary>(mesher, arguments_.rebate, 0,
                                         FdmDirichletBoundary::Lower));

        }
        if (   arguments_.barrierType == Barrier::UpIn
            || arguments_.barrierType == Barrier::UpOut) {
            boundaries.push_back(
				boost::make_shared<FdmDirichletBoundary>(mesher, arguments_.rebate, 0,
                                         FdmDirichletBoundary::Upper));
        }

        // 5. Solver
        FdmSolverDesc solverDesc = { mesher, boundaries, conditions,
                                     calculator, maturity,
                                     tGrid_, dampingSteps_ };

        boost::shared_ptr<FdmHestonSolver> solver(boost::make_shared<FdmHestonSolver>(
                    Handle<HestonProcess>(process), solverDesc, schemeDesc_,
                    Handle<FdmQuantoHelper>(), leverageFct_));

        const Real spot = process->s0()->value();
        results_.value = solver->valueAt(spot, process->v0());
        results_.delta = solver->deltaAt(spot, process->v0());
        results_.gamma = solver->gammaAt(spot, process->v0());
        results_.theta = solver->thetaAt(spot, process->v0());

        // 6. Calculate vanilla option and rebate for in-barriers
        if (   arguments_.barrierType == Barrier::DownIn
            || arguments_.barrierType == Barrier::UpIn) {
            // Cast the payoff
            boost::shared_ptr<StrikedTypePayoff> payoff =
                    boost::dynamic_pointer_cast<StrikedTypePayoff>(
                                                            arguments_.payoff);
            // Calculate the vanilla option
            boost::shared_ptr<DividendVanillaOption> vanillaOption(
				boost::make_shared<DividendVanillaOption>(payoff,arguments_.exercise,
                                          dividendCondition->dividendDates(),
                                          dividendCondition->dividends()));
            vanillaOption->setPricingEngine(boost::shared_ptr<PricingEngine>(
				boost::make_shared<FdHestonVanillaEngine>(*model_, tGrid_, xGrid_,
                                              vGrid_, dampingSteps_,
                                              schemeDesc_)));
            // Calculate the rebate value
            boost::shared_ptr<DividendBarrierOption> rebateOption(
				boost::make_shared<DividendBarrierOption>(arguments_.barrierType,
                                          arguments_.barrier,
                                          arguments_.rebate,
                                          payoff, arguments_.exercise,
                                          dividendCondition->dividendDates(),
                                          dividendCondition->dividends()));
            const Size xGridMin = 20;
            const Size vGridMin = 10;
            const Size rebateDampingSteps
                = (dampingSteps_ > 0) ? std::min(Size(1), dampingSteps_/2) : 0;
            rebateOption->setPricingEngine(
				boost::make_shared<FdHestonRebateEngine>(*model_, tGrid_,
                                             std::max(xGridMin, xGrid_/4),
                                             std::max(vGridMin, vGrid_/4),
                                             rebateDampingSteps,
                                             schemeDesc_));

            results_.value = vanillaOption->NPV()   + rebateOption->NPV()
                                                    - results_.value;
            results_.delta = vanillaOption->delta() + rebateOption->delta()
                                                    - results_.delta;
            results_.gamma = vanillaOption->gamma() + rebateOption->gamma()
                                                    - results_.gamma;
            results_.theta = vanillaOption->theta() + rebateOption->theta()
                                                    - results_.theta;
        }
    }
}


/*    Copyright (c) 2010-2019, Delft University of Technology
 *    All rigths reserved
 *
 *    This file is part of the Tudat. Redistribution and use in source and
 *    binary forms, with or without modification, are permitted exclusively
 *    under the terms of the Modified BSD license. You should have received
 *    a copy of the license with this file. If not, please or visit:
 *    http://tudat.tudelft.nl/LICENSE.
 */

#include <boost/make_shared.hpp>

#include "Tudat/Astrodynamics/Gravitation/tabulatedGravityFieldVariations.h"

namespace tudat
{

namespace gravitation
{

//! Class constructor
TabulatedGravityFieldVariations::TabulatedGravityFieldVariations(
        const std::map< double, Eigen::MatrixXd >& cosineCoefficientCorrections,
        const std::map< double, Eigen::MatrixXd >& sineCoefficientCorrections,
        const int minimumDegree, const int minimumOrder,
        const std::shared_ptr< interpolators::InterpolatorSettings >interpolatorType ):
    GravityFieldVariations( minimumDegree, minimumOrder,
                            minimumDegree + cosineCoefficientCorrections.begin( )->second.rows( ) - 1,
                            minimumOrder + cosineCoefficientCorrections.begin( )->second.cols( ) - 1 ),
    interpolatorType_( interpolatorType )
{
    // Create interpolator for tabulated coefficients.
    resetCoefficientInterpolator( cosineCoefficientCorrections, sineCoefficientCorrections );
}

//! Function to (re)set the tabulated spherical harmonic coefficients.
void TabulatedGravityFieldVariations::resetCoefficientInterpolator(
        const std::map< double, Eigen::MatrixXd >& cosineCoefficientCorrections,
        const std::map< double, Eigen::MatrixXd >& sineCoefficientCorrections )
{
    // Set current coefficient tables.
    cosineCoefficientCorrections_ = cosineCoefficientCorrections;
    sineCoefficientCorrections_ = sineCoefficientCorrections;

    // Check consistency of map sizes.
    if( cosineCoefficientCorrections_.size( ) != sineCoefficientCorrections_.size( ) )
    {
        throw std::runtime_error( "Error when resetting tabulated gravity field corrections, sine and cosine data size incompatible" );
    }

    // Create iterators over maps.
    std::map< double, Eigen::MatrixXd >::iterator cosineIterator =
            cosineCoefficientCorrections_.begin( );
    std::map< double, Eigen::MatrixXd >::iterator sineIterator =
            sineCoefficientCorrections_.begin( );

    // Declare map to store concatenated (horizontally) [cosine|sine] coefficients for
    // given discrete times.
    std::map< double, Eigen::MatrixXd > sineCosinePairMap;

    // Declare matrix to set concatenated (horizontally) [cosine|sine] coefficients at
    // current time in loop.
    Eigen::MatrixXd currentCoefficients = Eigen::MatrixXd::Zero( numberOfDegrees_, 2 * numberOfOrders_ );

    // Iterate over all times in correction maps.
    for( unsigned int i = 0; i < cosineCoefficientCorrections_.size( ); i++ )
    {
        // Check whether input times are consistent
        if( cosineIterator->first != sineIterator->first )
        {
            std::string errorMessage = "Error when resetting tabulated gravity field corrections, sine and cosine data time differ by" +
                    std::to_string(  cosineIterator->first - sineIterator->first );
            throw std::runtime_error( errorMessage );
        }
        // Check whether matrix sizes are consistent.
        if( ( cosineIterator->second.rows( ) != numberOfDegrees_ ) ||
                ( sineIterator->second.rows( ) != numberOfDegrees_ ) ||
                ( cosineIterator->second.cols( ) != numberOfOrders_ ) ||
                ( sineIterator->second.cols( ) != numberOfOrders_ ) )
        {
            std::string errorMessage = "Error when resetting tabulated gravity field corrections, sine and cosine blocks of inconsistent size";
            throw std::runtime_error( errorMessage );
        }

        // Concatenate cosine and sine matrices
        currentCoefficients.block( 0, 0, numberOfDegrees_, numberOfOrders_ ) =
                cosineIterator->second;
        currentCoefficients.block( 0, numberOfOrders_, numberOfDegrees_, numberOfOrders_ ) =
                sineIterator->second;

        // Set as entry in map of concatenated matrices
        sineCosinePairMap[ cosineIterator->first ] = currentCoefficients;

        // Increment iterators.
        cosineIterator++;
        sineIterator++;
    }

    // Create interpolator
    variationInterpolator_ =
            interpolators::createOneDimensionalInterpolator< double, Eigen::MatrixXd >(
                sineCosinePairMap, interpolatorType_ );
}

//! Function for calculating corrections by interpolating tabulated corrections.
std::pair< Eigen::MatrixXd, Eigen::MatrixXd > TabulatedGravityFieldVariations::
calculateSphericalHarmonicsCorrections(
        const double time )
{
    // Interpolate corrections
    Eigen::MatrixXd cosineSinePair = variationInterpolator_->interpolate( time );

    // Split interpolated concatenated matrix and return.
    return std::make_pair( cosineSinePair.block( 0, 0, numberOfDegrees_, numberOfOrders_ ),
                           cosineSinePair.block( 0, numberOfOrders_, numberOfDegrees_, numberOfOrders_ ) );
}

} // namespace gravitation

} // namespace tudat


#include "check_particle.h"
#include "opic_fwd.h"
#include "config.h"
#include "io_utilities.h"
#include "grid.h"
#include "particles.h"
#include "gather_scatter.h"
#include "call_lua_function.h"

#include <boost/format.hpp>
#include <omp.h>
#include <stdexcept>

 /*********************************************************
 * Check if particle is active.                           *
 * equation of motion is solved for active particles only *
 * return true if particles is active, otherwise - false  *
 *********************************************************/

bool is_particle_can_scatter(Particles& particles, index_t p, const Grid& grid, const DblVector& dr)
{
    if (particles.is_inactive(p))
        return false;

    const Particle& particle = particles[p];
    const double h = PIC::Config::h();
    const index_t pi = static_cast<index_t>(floor((particle.r.x + dr.x)/h));
    const index_t pj = static_cast<index_t>(floor((particle.r.y + dr.y)/h));
    const index_t pk = static_cast<index_t>(floor((particle.r.z + dr.z)/h));
    const PIC::CellState home_cell_state = grid(pi, pj, pk).state();

    if (pi < 1 || pi > grid.size_x() - 2 ||
        pj < 1 || pj > grid.size_y() - 2 ||
        pk < 1 || pk > grid.size_z() - 2 ||
        home_cell_state == PIC::cs_absorptive)
    {
        particles.remove_later(p);
        return false;
    }

    return true;
}

bool is_particle_can_move(Particles& particles, index_t p, const Grid& grid)
{
    if (!is_particle_can_scatter(particles, p, grid, DblVector()))
        return false;

    Particle& particle = particles[p];
    const double h = PIC::Config::h();
    const index_t pi = static_cast<index_t>(floor(particle.r.x/h));
    const index_t pj = static_cast<index_t>(floor(particle.r.y/h));
    const index_t pk = static_cast<index_t>(floor(particle.r.z/h));
    const PIC::CellState home_cell_state = grid(pi,pj,pk).state();

    if (home_cell_state == PIC::cs_active)
        return true;

    if (home_cell_state == PIC::cs_custom)
        return lua_validate_particle(particle);

    // never reach here
    return false;
}

bool check_particle_move(const Particle& particle, const Grid& grid, const DblVector& dr)
{
    if (PIC::Config::CFL_severity() == PIC::Ignore)
        return true;

    const double h = PIC::Config::h();
    const double h_2 = PIC::Config::h_2();

    if (fabs(dr.x) > h_2 || fabs(dr.y) > h_2 || fabs(dr.z) > h_2)
    {
        const index_t step = PIC::Config::current_time_step();

        Grid::NodeType point_val;
        PIC::from_grid_to_point(grid, particle.r, point_val);

        const index_t pi = static_cast<index_t>(particle.r.x/h);
        const index_t pj = static_cast<index_t>(particle.r.y/h);
        const index_t pk = static_cast<index_t>(particle.r.z/h);

        const std::string msg = boost::str(boost::format("Step = %1%:\n\tError CFL, particle shift dr should be  < h/2:"
                                                  "\n\tparticle's home cell = (%2%, %3%, %4%);"
                                                  "\n\tgrid size = (%5% x %6% x %7%);"
                                                  "\n\tdr = (%8%, %9%, %10%),\n\tdr.abs() = %11%; h/2 = %12%;"
                                                  "\n\tparticle [%13%] at (%14%, %15%, %16%)"
                                                  "\n\tgrid values at particle position:"
                                                  "\n\t\t B = %17%, Bx = %18%, By = %19%, Bz = %20%"
                                                  "\n\t\t E = %21%, Ex = %22%, Ey = %23%, Ez = %24%"
                                                  "\n\t\t NP = %25%, UPx = %26%, UPy = %27%, UPz = %28%"
                                                  "\n\t\t UEx = %29%, UEy = %30%, UEz = %31%"
                                                  "\n\t\t cell state = %32% (0 - active, 1 - absorptive, 2 - custom)")
                                                  % step
                                                  % pi % pj % pk
                                                  % grid.size_x() % grid.size_y() % grid.size_z()
                                                  % dr.x % dr.y % dr.z % (dr.abs())
                                                  % h_2
                                                  % particle.group_name
                                                  % particle.r.x % particle.r.y % particle.r.z
                                                  % point_val.B.abs() % point_val.B.x % point_val.B.y % point_val.B.z
                                                  % point_val.E.abs() % point_val.E.x % point_val.E.y % point_val.E.z
                                                  % point_val.NP % point_val.UP.x % point_val.UP.y % point_val.UP.z
                                                  % point_val.UE.x % point_val.UE.y % point_val.UE.z
                                                  % grid(pi,pj,pk).state());

        const int tid = omp_get_thread_num();
        const std::string log_file_name = str(boost::format("opic_thread_%1%_check_particle_move_err.log") % tid);
        std::ofstream ofs_log(log_file_name.c_str(), std::ios_base::app);
        ofs_log << msg << std::endl;
        return false;
    }

    return true;
}