#/*##########################################################################
# Copyright (C) 2001-2016 European Synchrotron Radiation Facility
#
#              PyHST2
#  European Synchrotron Radiation Facility, Grenoble,Franceg
#
# PyHST2 is  developed at
# the ESRF by the Scientific Software  staff.
# Principal author for PyHST2: Alessandro Mirone.
#
# 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.
#
# PyHST2 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
# PyHST2; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
# Suite 330, Boston, MA 02111-1307, USA.
#
# PyHST2 follows the dual licensing model of Trolltech's Qt and Riverbank's PyQt
# and cannot be used as a free plugin for a non-free program.
#
# Please contact the ESRF industrial unit (industry@esrf.fr) if this license
# is a problem for you.
#############################################################################*/
using namespace std;

#include<math_constants.h>

/////// #define CUDART_INF_F __int_as_float(0x7f800000)

// #include<iostream.h>

#include <stdio.h>
#include <stdlib.h>
#include<math.h>
#include <cuda.h>
#include <cublas.h>
#include <cuComplex.h>
#include<time.h>

#ifndef HUGE
#include<values.h>
#define HUGE MAXFLOAT
#endif

#define FROMCU

extern "C" {
#include<CCspace.h>
}
// #include <cutil.h>
#include<assert.h>


#define NO_USETHRUST
#ifdef USETHRUST
#include <thrust/device_vector.h>
#include <thrust/sequence.h>
#include <thrust/sort.h>
#include <thrust/inner_product.h>
#include <thrust/device_ptr.h>
#endif

#define USEDD

#  define CUDACHECK \
  { cudaThreadSynchronize(); \
    cudaError_t last = cudaGetLastError();\
    if(last!=cudaSuccess) {\
      printf("ERRORX: %s  %s  %i \n", cudaGetErrorString( last),    __FILE__, __LINE__    );    \
      exit(1);\
    }\
  }

// #  define CUDACHECK

#define DOUBLE_SCAL
#ifdef  DOUBLE_SCAL
#define PSUMTYPE double


#define PRECOND_NBINS
#define TWO2by2 2


//---------------------------------------------
// void qdplot(float* arr, int L) {

//   FILE* fid = fopen("tmp_plot.dat","wb");
//   if (fid == NULL) return;
//   fwrite(arr,L*sizeof(float),1,fid);
//   fclose(fid);
//   system("./plotbinary.sh tmp_plot.dat");

// }
//--------------------------------------------
__global__ void Vector_Dot_Product (int N,  const float *V1 , const float *V2 , double *V3   )
{
  volatile __shared__ double chache[512] ;
  double temp =0.0;
  int gid = blockDim.x * blockIdx.x + threadIdx.x ;
  const unsigned int tid = threadIdx.x ;
  while ( gid < N )
    {
      temp += V1[gid] * V2[gid] ;
      gid += blockDim.x * gridDim.x ;
    }
  chache[tid] = temp ;
  __syncthreads () ;
  
  {  if(tid<256) { chache[tid] += chache [tid + 256]; } __syncthreads();  }
  {  if(tid<128) { chache[tid] += chache [tid + 128]; } __syncthreads();  }
  {  if(tid<64) { chache[tid]  += chache [tid + 64]; } __syncthreads();  }
  if(tid<32) {
    chache[tid]  += chache [tid + 32];
    chache[tid]  += chache [tid + 16];
    chache[tid]  += chache [tid + 8];
    chache[tid]  += chache [tid + 4];
    chache[tid]  += chache [tid + 2];
    chache[tid]  += chache [tid + 1];
  }
  if ( tid == 0 ) {
    V3[blockIdx.x] = chache [0] ;
  } 
}


#  define CUDA_SAFE_CALL_NO_SYNC( call) {				\
    cudaError err = call;						\
    if( cudaSuccess != err) {						\
      fprintf(stderr, "Cuda error in file '%s' in line %i : %s.\n",	\
	      __FILE__, __LINE__, cudaGetErrorString( err) );		\
      exit(EXIT_FAILURE);						\
    } }

#  define CUDA_SAFE_CALL( call)     CUDA_SAFE_CALL_NO_SYNC(call);	\

#define USECUDADOT 0
double cudaDot( int N, float *   V1_D , float * V2_D  )  {
  int blockPerGrid=  min( 0xFFFF , (N+512-1) / 512 );
  double *V3_D;
  cudaMalloc ( (void **)&V3_D , blockPerGrid*sizeof(double)) ;
  CUDA_SAFE_CALL(cudaMemset(V3_D, 0, blockPerGrid  * sizeof(double)));

  
  CUDACHECK;
  double *D_ones;
  cudaMalloc ( (void **)&D_ones , 1*sizeof(double)) ;
  double one=1.0;
  cudaMemcpy(D_ones,&one, 1*sizeof(double), cudaMemcpyHostToDevice);
  CUDACHECK;
  Vector_Dot_Product <<<blockPerGrid , 512 >>> (N , V1_D , V2_D , V3_D ) ;
  CUDACHECK;
  double sum =   cublasDdot(  blockPerGrid  , D_ones , 0, V3_D , 1)     ;
  CUDACHECK;
  cudaFree( D_ones) ;
  CUDACHECK;
  cudaFree( V3_D) ;
  CUDACHECK;
  return sum;
}

// #define cublasSdot  do_dot_product
double do_dot_product( int n, float *A  , int stride1  , float *B , int  stride2 )
{
  return cudaDot( n ,  A, B  ) ;
}


#else
#define PSUMTYPE float
#endif

#define WKSIZE 256


//Added required header and error handle macro for CUFFT. CUFFT returns DIFFERENT TYPE of error messages.

#include <cufft.h>
#define CUDA_SAFE_FFT(call){						\
    cufftResult err = call;						\
    if( CUFFT_SUCCESS != err) {						\
      fprintf(stderr, "Cuda error in file '%s' in line %i : %d.\n",	\
	      __FILE__, __LINE__, err );				\
      exit(EXIT_FAILURE);						\
    } }

#define CUFFT_SAFE_CALL(call, extramsg) {do {				\
      cufftResult err = call;						\
      if( CUFFT_SUCCESS  != err) {					\
	if(err==   CUFFT_INVALID_PLAN   )				\
	  sprintf(messaggio, "Error in file '%s' in line %i :%s -- %s \n", \
		  __FILE__, __LINE__, "CUFFT_INVALID_PLAN"    ,  extramsg); \
	if(err==  CUFFT_ALLOC_FAILED   )				\
	  sprintf(messaggio, "Error in file '%s' in line %i :%s -- %s \n", \
		  __FILE__, __LINE__, "CUFFT_ALLOC_FAILED"    ,  extramsg); \
	if(err==  CUFFT_INVALID_TYPE   )				\
	  sprintf(messaggio, "Error in file '%s' in line %i :%s -- %s \n", \
		  __FILE__, __LINE__,  "CUFFT_INVALID_TYPE"  ,  extramsg); \
	if(err==  CUFFT_INVALID_VALUE  )				\
	  sprintf(messaggio, "Error in file '%s' in line %i :%s -- %s \n", \
		  __FILE__, __LINE__, "CUFFT_INVALID_VALUE"   ,  extramsg); \
	if(err==    CUFFT_INTERNAL_ERROR)				\
	  sprintf(messaggio, "Error in file '%s' in line %i :%s -- %s \n", \
		  __FILE__, __LINE__, "CUFFT_INTERNAL_ERROR"   ,  extramsg); \
	if(err==  CUFFT_EXEC_FAILED )					\
	  sprintf(messaggio, "Error in file '%s' in line %i :%s -- %s \n", \
		  __FILE__, __LINE__, "CUFFT_EXEC_FAILED"     ,  extramsg); \
	if(err==    CUFFT_SETUP_FAILED )				\
	  sprintf(messaggio, "Error in file '%s' in line %i :%s -- %s \n", \
		  __FILE__, __LINE__, "CUFFT_SETUP_FAILED"   ,  extramsg); \
	if(err==  CUFFT_INVALID_SIZE)					\
	  sprintf(messaggio, "Error in file '%s' in line %i :%s -- %s \n", \
		  __FILE__, __LINE__, "CUFFT_INVALID_SIZE"   ,  extramsg); \
	fprintf(stderr, "%s" , messaggio );				\
	exit(EXIT_FAILURE);						\
									\
      } } while (0) ;}


//#define printf(msg) fprintf(stderr,msg)




// -----------------------------------------------------------------------------------------------
texture<float, 2, cudaReadModeElementType> texProjes;

texture<float2, 2, cudaReadModeElementType> texcProjes;

texture<float, 3, cudaReadModeElementType> texfor3D;  // 3D texture
cudaChannelFormatDesc floatTex = cudaCreateChannelDesc<float>();



inline __host__ __device__ void operator+=(float2 &a, float2 b)
{
    a.x += b.x;
    a.y += b.y;
}

__global__ static void    gputomo_kernel32_2by2(int num_proj,int num_bins, float axis_position,
						float2 *d_SLICE,
						float gpu_offset_x, float gpu_offset_y , float * d_cos_s , float * d_sin_s ,float *  d_axis_s
						) {
  const int tidx = threadIdx.x;
  const int bidx = blockIdx.x;
  const int tidy = threadIdx.y;
  const int bidy = blockIdx.y;

  __shared__  float   shared[768] ;
  float  * sh_sin  = shared;
  float  * sh_cos  = shared+256;
  float  * sh_axis = sh_cos+256;

  float pcos, psin;
  float h0, h1, h2, h3;
  //  l'asse e' dato a partire dall' angolo  primo pixel, ma i centri dei pixel sono nel mezzo
  const float apos_off_x= gpu_offset_x   -  axis_position ;
  const float apos_off_y= gpu_offset_y   -  axis_position ;
  float acorr05;
  float2 res0, res1, res2, res3;

  res0=(float2){0.f,0.f};
  res1=(float2){0.f,0.f};
  res2=(float2){0.f,0.f};
  res3=(float2){0.f,0.f};
  


  const float bx00 = (32 * bidx  + 2 * tidx   + 0 +   apos_off_x  ) ;
  const float by00 = (32 * bidy  + 2 * tidy   + 0 +   apos_off_y  ) ;


  int lettofino=0;  // letto= read   fino=till
  for(int proje=0; proje<num_proj; proje++) {
    if(proje>=lettofino) {
      __syncthreads();
      int ip = tidy*16+tidx;
      if( lettofino+ip < num_proj) {
	sh_cos [ip] = d_cos_s[lettofino+ip] ; // populating the shared memory
	sh_sin [ip] = d_sin_s[lettofino+ip] ;
	sh_axis[ip] = d_axis_s[lettofino+ip] ;
      }
      lettofino=lettofino+256; // 256=16*16 block size
      __syncthreads();
    }
    pcos = sh_cos[ -(lettofino-256)+ proje] ;
    psin = sh_sin[-(lettofino-256)+ proje] ;

    acorr05=   sh_axis[-(lettofino-256) +proje] ;

    h0 =  (acorr05+
	   bx00*pcos -
	   by00*psin
	   ) ;
    h1 =  (acorr05+
	   (bx00+0)*pcos -
	   (by00+1)*psin
	   ) ;
    h2 =  (acorr05+
	   (bx00+1)*pcos -
	   (by00+0)*psin
	   ) ;
    h3 =  (acorr05+
	   (bx00+1)*pcos -
	   (by00+1)*psin
	   ) ;

    if(h0>=0 && h0<num_bins)
      res0 += tex2D(texcProjes,h0 +0.5f,proje  +0.5f ) ;

    if(h1>=0 && h1<num_bins)
      res1 += tex2D(texcProjes,h1 +0.5f,proje  +0.5f ) ;

    if(h2>=0 && h2<num_bins)
      res2 += tex2D(texcProjes,h2 +0.5f,proje  +0.5f ) ;

    if(h3>=0 && h3<num_bins)
      res3 += tex2D(texcProjes,h3 +0.5f,proje  +0.5f ) ;
  };
  d_SLICE[ 32*gridDim.x*(bidy*32+tidy*2+0) + bidx*32 + tidx*2 + 0     ] = res0;
  d_SLICE[ 32*gridDim.x*(bidy*32+tidy*2+1) + bidx*32 + tidx*2 + 0     ] = res1;
  d_SLICE[ 32*gridDim.x*(bidy*32+tidy*2+0) + bidx*32 + tidx*2 + 1     ] = res2;
  d_SLICE[ 32*gridDim.x*(bidy*32+tidy*2+1) + bidx*32 + tidx*2 + 1     ] = res3;
}






__global__ static void    gputomo_kernel32(int num_proj,int num_bins, float axis_position,
					   float *d_SLICE,
					   float gpu_offset_x, float gpu_offset_y , float * d_cos_s , float * d_sin_s ,float *  d_axis_s
					   ) {
  const int tidx = threadIdx.x;
  const int bidx = blockIdx.x;
  const int tidy = threadIdx.y;
  const int bidy = blockIdx.y;

  __shared__  float   shared[768] ;
  float  * sh_sin  = shared;
  float  * sh_cos  = shared+256;
  float  * sh_axis = sh_cos+256;

  float pcos, psin;
  float h0, h1, h2, h3;
  //  l'asse e' dato a partire dall' angolo  primo pixel, ma i centri dei pixel sono nel mezzo
  const float apos_off_x= gpu_offset_x   -  axis_position ;
  const float apos_off_y= gpu_offset_y   -  axis_position ;
  float acorr05;
  float res0, res1, res2, res3;

  res0=0;
  res1=0;
  res2=0;
  res3=0;


  const float bx00 = (32 * bidx  + 2 * tidx   + 0 +   apos_off_x  ) ;
  const float by00 = (32 * bidy  + 2 * tidy   + 0 +   apos_off_y  ) ;


  int lettofino=0;  // letto= read   fino=till
  for(int proje=0; proje<num_proj; proje++) {
    if(proje>=lettofino) {
      __syncthreads();
      int ip = tidy*16+tidx;
      if( lettofino+ip < num_proj) {
	sh_cos [ip] = d_cos_s[lettofino+ip] ; // populating the shared memory
	sh_sin [ip] = d_sin_s[lettofino+ip] ;
	sh_axis[ip] = d_axis_s[lettofino+ip] ;
      }
      lettofino=lettofino+256; // 256=16*16 block size
      __syncthreads();
    }
    pcos = sh_cos[ -(lettofino-256)+ proje] ;
    psin = sh_sin[-(lettofino-256)+ proje] ;

    acorr05=   sh_axis[-(lettofino-256) +proje] ;

    h0 =  (acorr05+
	   bx00*pcos -
	   by00*psin
	   ) ;
    h1 =  (acorr05+
	   (bx00+0)*pcos -
	   (by00+1)*psin
	   ) ;
    h2 =  (acorr05+
	   (bx00+1)*pcos -
	   (by00+0)*psin
	   ) ;
    h3 =  (acorr05+
	   (bx00+1)*pcos -
	   (by00+1)*psin
	   ) ;

    if(h0>=0 && h0<num_bins)
      res0=res0+tex2D(texProjes,h0 +0.5f,proje  +0.5f ) ;

    if(h1>=0 && h1<num_bins)
      res1=res1+tex2D(texProjes,h1 +0.5f,proje  +0.5f ) ;

    if(h2>=0 && h2<num_bins)
      res2=res2+tex2D(texProjes,h2 +0.5f,proje  +0.5f ) ;

    if(h3>=0 && h3<num_bins)
      res3=res3+tex2D(texProjes,h3 +0.5f,proje  +0.5f ) ;
  };
  d_SLICE[ 32*gridDim.x*(bidy*32+tidy*2+0) + bidx*32 + tidx*2 + 0     ] = res0;
  d_SLICE[ 32*gridDim.x*(bidy*32+tidy*2+1) + bidx*32 + tidx*2 + 0     ] = res1;
  d_SLICE[ 32*gridDim.x*(bidy*32+tidy*2+0) + bidx*32 + tidx*2 + 1     ] = res2;
  d_SLICE[ 32*gridDim.x*(bidy*32+tidy*2+1) + bidx*32 + tidx*2 + 1     ] = res3;
}


__global__ static void    gputomo_kernel32_heli(int num_proj,int num_bins, float axis_position,
						float *d_SLICE,
						float gpu_offset_x, float gpu_offset_y   ,
						float * d_cos_s , float * d_sin_s ,float *  d_axis_s,
						int inizio, int fine,
						float fattore_estremi,
						int ipoffset
						) {
  const int tidx = threadIdx.x;
  const int bidx = blockIdx.x;
  const int tidy = threadIdx.y;
  const int bidy = blockIdx.y;

  __shared__  float   shared[768] ;
  float  * sh_sin  = shared;
  float  * sh_cos  = shared+256;
  float  * sh_axis = sh_cos+256;

  float pcos, psin;
  float h0, h1, h2, h3;
  //  l'asse e' dato a partire dall' angolo  primo pixel, ma i centri dei pixel sono nel mezzo
  const float apos_off_x= gpu_offset_x   -  axis_position ;
  const float apos_off_y= gpu_offset_y   -  axis_position ;
  float acorr05;
  float res0, res1, res2, res3;

  res0=0;
  res1=0;
  res2=0;
  res3=0;


  const float bx00 = (32 * bidx  + 2 * tidx   + 0 +   apos_off_x  ) ;
  const float by00 = (32 * bidy  + 2 * tidy   + 0 +   apos_off_y  ) ;


  int lettofino=ipoffset+inizio;  // letto= read   fino=till
  for(int proje=ipoffset+inizio; proje<ipoffset+fine; proje++) {
    if(proje>=lettofino) {
      __syncthreads();
      int ip = tidy*16+tidx;
      if( lettofino+ip < num_proj) {
	sh_cos [ip] = d_cos_s[lettofino+ip] ; // populating the shared memory
	sh_sin [ip] = d_sin_s[lettofino+ip] ;
	sh_axis[ip] = d_axis_s[lettofino+ip] ;
      }
      lettofino=lettofino+256; // 256=16*16 block size
      __syncthreads();
    }
    pcos = sh_cos[ -(lettofino-256)+ proje] ;
    psin = sh_sin[-(lettofino-256)+ proje] ;

    acorr05=   sh_axis[-(lettofino-256) +proje] ;

    h0 =  (acorr05+
	   bx00*pcos -
	   by00*psin
	   ) ;
    h1 =  (acorr05+
	   (bx00+0)*pcos -
	   (by00+1)*psin
	   ) ;
    h2 =  (acorr05+
	   (bx00+1)*pcos -
	   (by00+0)*psin
	   ) ;
    h3 =  (acorr05+
	   (bx00+1)*pcos -
	   (by00+1)*psin
	   ) ;

    if(proje==ipoffset+inizio || proje==(ipoffset+fine-1) ) {
      if(h0>=0 && h0<num_bins)
	res0=res0+tex2D(texProjes,h0 +0.5f,proje-ipoffset  +0.5f )*fattore_estremi ;
      if(h1>=0 && h1<num_bins)
	res1=res1+tex2D(texProjes,h1 +0.5f,proje-ipoffset  +0.5f )*fattore_estremi ;
      if(h2>=0 && h2<num_bins)
	res2=res2+tex2D(texProjes,h2 +0.5f,proje-ipoffset  +0.5f )*fattore_estremi ;
      if(h3>=0 && h3<num_bins)
	res3=res3+tex2D(texProjes,h3 +0.5f,proje-ipoffset  +0.5f )*fattore_estremi ;
    } else {
      if(h0>=0 && h0<num_bins)
	res0=res0+tex2D(texProjes,h0 +0.5f,proje-ipoffset  +0.5f ) ;
      if(h1>=0 && h1<num_bins)
	res1=res1+tex2D(texProjes,h1 +0.5f,proje-ipoffset  +0.5f ) ;
      if(h2>=0 && h2<num_bins)
	res2=res2+tex2D(texProjes,h2 +0.5f,proje-ipoffset  +0.5f ) ;
      if(h3>=0 && h3<num_bins)
	res3=res3+tex2D(texProjes,h3 +0.5f,proje-ipoffset  +0.5f ) ;
    }
  };
  d_SLICE[ 32*gridDim.x*(bidy*32+tidy*2+0) + bidx*32 + tidx*2 + 0     ] = res0;
  d_SLICE[ 32*gridDim.x*(bidy*32+tidy*2+1) + bidx*32 + tidx*2 + 0     ] = res1;
  d_SLICE[ 32*gridDim.x*(bidy*32+tidy*2+0) + bidx*32 + tidx*2 + 1     ] = res2;
  d_SLICE[ 32*gridDim.x*(bidy*32+tidy*2+1) + bidx*32 + tidx*2 + 1     ] = res3;
}




__global__ static void    gputomo_kernel32_heli_2by2(int num_proj,int num_bins, float axis_position,
						     float2 *d_SLICE,
						     float gpu_offset_x, float gpu_offset_y   ,
						     float * d_cos_s , float * d_sin_s ,float *  d_axis_s,
						     int2 inizio2, int2 fine2,
						     float2 fattore_estremi2,
						     int ipoffset
						     ) {
  const int tidx = threadIdx.x;
  const int bidx = blockIdx.x;
  const int tidy = threadIdx.y;
  const int bidy = blockIdx.y;

  __shared__  float   shared[768] ;
  float  * sh_sin  = shared;
  float  * sh_cos  = shared+256;
  float  * sh_axis = sh_cos+256;

  float pcos, psin;
  float h0, h1, h2, h3;
  //  l'asse e' dato a partire dall' angolo  primo pixel, ma i centri dei pixel sono nel mezzo
  const float apos_off_x= gpu_offset_x   -  axis_position ;
  const float apos_off_y= gpu_offset_y   -  axis_position ;
  float acorr05;
  float2 res0, res1, res2, res3;
  float2 tmp_f2;
  float tmp0[2];
  float tmp1[2];
  float tmp2[2];
  float tmp3[2];

  float Vres0[2], Vres1[2], Vres2[2], Vres3[2];

  Vres0[0 ]=0;
  Vres1[0 ]=0;
  Vres2[0 ]=0;
  Vres3[0 ]=0;
  Vres0[1 ]=0;
  Vres1[1 ]=0;
  Vres2[1 ]=0;
  Vres3[1 ]=0;
  
  int fattore_estremi[2];
  fattore_estremi[0] = fattore_estremi2.x;
  fattore_estremi[1] = fattore_estremi2.y;

  int inizio[2];
  inizio[0] = inizio2.x;
  inizio[1] = inizio2.y;
  
  int fine[2];
  fine[0] = fine2.x;
  fine[1] = fine2.y;

  int myinizio = min(inizio[0], inizio[1] ) ; 
  int myfine   = min(fine  [0], fine  [1] ) ; 


  const float bx00 = (32 * bidx  + 2 * tidx   + 0 +   apos_off_x  ) ;
  const float by00 = (32 * bidy  + 2 * tidy   + 0 +   apos_off_y  ) ;
    
  int lettofino=ipoffset+myinizio;  // letto= read   fino=till
  for(int proje=ipoffset+myinizio; proje<ipoffset+myfine; proje++) {
    if(proje>=lettofino) {
      __syncthreads();
      int ip = tidy*16+tidx;
      if( lettofino+ip < num_proj) {
	sh_cos [ip] = d_cos_s[lettofino+ip] ; // populating the shared memory
	sh_sin [ip] = d_sin_s[lettofino+ip] ;
	sh_axis[ip] = d_axis_s[lettofino+ip] ;
      }
      lettofino=lettofino+256; // 256=16*16 block size
      __syncthreads();
    }
    pcos = sh_cos[ -(lettofino-256)+ proje] ;
    psin = sh_sin[-(lettofino-256)+ proje] ;
    
    acorr05=   sh_axis[-(lettofino-256) +proje] ;
    
    h0 =  (acorr05+
	   bx00*pcos -
	   by00*psin
	   ) ;
    h1 =  (acorr05+
	   (bx00+0)*pcos -
	   (by00+1)*psin
	   ) ;
    h2 =  (acorr05+
	   (bx00+1)*pcos -
	   (by00+0)*psin
	   ) ;
    h3 =  (acorr05+
	   (bx00+1)*pcos -
	   (by00+1)*psin
	   ) ;

    tmp_f2 = tex2D(texcProjes,h0 +0.5f,proje-ipoffset  +0.5f );
    tmp0[0] = tmp_f2.x ;
    tmp0[1] = tmp_f2.y;
    
    tmp_f2 = tex2D(texcProjes,h1 +0.5f,proje-ipoffset  +0.5f );
    tmp1[0] = tmp_f2.x ;
    tmp1[1] = tmp_f2.y;
    
    tmp_f2 = tex2D(texcProjes,h2 +0.5f,proje-ipoffset  +0.5f );
    tmp2[0] = tmp_f2.x ;
    tmp2[1] = tmp_f2.y;
    
    tmp_f2 = tex2D(texcProjes,h3 +0.5f,proje-ipoffset  +0.5f );
    tmp3[0] = tmp_f2.x ;
    tmp3[1] = tmp_f2.y;

    for(int ks = 0; ks<2; ks++) {
    
      if(proje==ipoffset+inizio[ks] || proje==(ipoffset+fine[ks]-1) ) {
	
	if(h0>=0 && h0<num_bins) 
	  Vres0[ks]=Vres0[ks]+tmp0[ks]*fattore_estremi[ks] ;
	
	if(h1>=0 && h1<num_bins)
	  Vres1[ks]=Vres1[ks]+tmp1[ks]*fattore_estremi[ks] ;
	
	if(h2>=0 && h2<num_bins)
	  Vres2[ks]=Vres2[ks]+tmp2[ks]*fattore_estremi[ks] ;
	
	if(h3>=0 && h3<num_bins)
	  Vres3[ks]=Vres3[ks]+tmp3[ks]*fattore_estremi[ks] ;
	
      } else {
	if(proje>ipoffset+inizio[ks] && proje < (ipoffset+fine[ks]-1) ) {
	
	  if(h0>=0 && h0<num_bins)
	    Vres0[ks]=Vres0[ks]+tmp0[ks] ;
	  if(h1>=0 && h1<num_bins)
	    Vres1[ks]=Vres1[ks]+tmp1[ks] ;
	  if(h2>=0 && h2<num_bins)
	    Vres2[ks]=Vres2[ks]+tmp2[ks] ;
	  if(h3>=0 && h3<num_bins)
	    Vres3[ks]=Vres3[ks]+tmp3[ks] ;
	}
      }
      
    }
    
    
  }
  res0.x = Vres0[0];
  res1.x = Vres1[0];
  res2.x = Vres2[0];
  res3.x = Vres3[0];
  res0.y = Vres0[1];
  res1.y = Vres1[1];
  res2.y = Vres2[1];
  res3.y = Vres3[1];
    
  d_SLICE[ 32*gridDim.x*(bidy*32+tidy*2+0) + bidx*32 + tidx*2 + 0     ] = res0;
  d_SLICE[ 32*gridDim.x*(bidy*32+tidy*2+1) + bidx*32 + tidx*2 + 0     ] = res1;
  d_SLICE[ 32*gridDim.x*(bidy*32+tidy*2+0) + bidx*32 + tidx*2 + 1     ] = res2;
  d_SLICE[ 32*gridDim.x*(bidy*32+tidy*2+1) + bidx*32 + tidx*2 + 1     ] = res3;
}







  __global__ static void    gputomo_kernel32_fan_heli(int num_proj,int num_bins, float axis_position,
						      float *d_SLICE,
						      float gpu_offset_x, float gpu_offset_y   , float * d_cos_s ,
						      float * d_sin_s ,float *  d_axis_s,
						      float dive,
						      float source_x,
						      int inizio, int fine,
						      float fattore_estremi,
						      int ipoffset
						      ) {
  const int tidx = threadIdx.x;
  const int bidx = blockIdx.x;
  const int tidy = threadIdx.y;
  const int bidy = blockIdx.y;

  __shared__  float   shared[768] ;
  float  * sh_sin  = shared;
  float  * sh_cos  = shared+256;
  float  * sh_axis = sh_cos+256;

  float pcos, psin;
  float h0, h1, h2, h3;
  //  l'asse e' dato a partire dall' angolo  primo pixel, ma i centri dei pixel sono nel mezzo
  const float apos_off_x= gpu_offset_x   -  axis_position ;
  const float apos_off_y= gpu_offset_y   -  axis_position ;
  float acorr05;
  float res0, res1, res2, res3;

  res0=0;
  res1=0;
  res2=0;
  res3=0;

  const float bx00 = (32 * bidx  + 2 * tidx   + 0 +   apos_off_x  ) ;
  const float by00 = (32 * bidy  + 2 * tidy   + 0 +   apos_off_y  ) ;

  int lettofino=ipoffset+inizio;  // letto= read   fino=till
  for(int proje=ipoffset+inizio; proje<ipoffset+fine; proje++) {
    if(proje>=lettofino) {
      __syncthreads();
      int ip = tidy*16+tidx;
      if( lettofino+ip < num_proj) {
	sh_cos [ip] = d_cos_s[lettofino+ip] ; // populating the shared memory
	sh_sin [ip] = d_sin_s[lettofino+ip] ;
	sh_axis[ip] = d_axis_s[lettofino+ip] ;
      }
      lettofino=lettofino+256; // 256=16*16 block size
      __syncthreads();
    }
    pcos = sh_cos[ -(lettofino-256)+ proje] ;
    psin = sh_sin[-(lettofino-256)+ proje] ;

    acorr05=   sh_axis[-(lettofino-256) +proje] ;

    h0 =  (acorr05+
	   bx00*pcos -
	   by00*psin
	   ) ;

    //     float corre = (-(h0  -source_x )     )*( bx00*psin +by00*pcos  )*dive;
    float corre = (h0  -source_x )*(1.0f/dive/(1.0f/dive +  bx00*psin +by00*pcos  )-1);


    h0 +=  corre ;

    h1 =  (acorr05+
	   (bx00+0)*pcos -
	   (by00+1)*psin
	   )+corre  ;
    h2 =  (acorr05+
	   (bx00+1)*pcos -
	   (by00+0)*psin
	   ) +corre ;
    h3 =  (acorr05+
	   (bx00+1)*pcos -
	   (by00+1)*psin
	   ) +corre ;

    if(proje==ipoffset+inizio || proje==(ipoffset+fine-1) ) {
      if(h0>=0 && h0<num_bins)
	res0=res0+tex2D(texProjes,h0 +0.5f,proje-ipoffset  +0.5f )*fattore_estremi  ;
      if(h1>=0 && h1<num_bins)
	res1=res1+tex2D(texProjes,h1 +0.5f,proje-ipoffset  +0.5f )*fattore_estremi  ;
      if(h2>=0 && h2<num_bins)
	res2=res2+tex2D(texProjes,h2 +0.5f,proje-ipoffset  +0.5f )*fattore_estremi  ;
      if(h3>=0 && h3<num_bins)
	res3=res3+tex2D(texProjes,h3 +0.5f,proje-ipoffset  +0.5f )*fattore_estremi  ;
    } else {
      if(h0>=0 && h0<num_bins)
	res0=res0+tex2D(texProjes,h0 +0.5f,proje-ipoffset  +0.5f ) ;
      if(h1>=0 && h1<num_bins)
	res1=res1+tex2D(texProjes,h1 +0.5f,proje-ipoffset  +0.5f ) ;
      if(h2>=0 && h2<num_bins)
	res2=res2+tex2D(texProjes,h2 +0.5f,proje-ipoffset  +0.5f ) ;
      if(h3>=0 && h3<num_bins)
	res3=res3+tex2D(texProjes,h3 +0.5f,proje-ipoffset  +0.5f ) ;
    }
  };
  if(1) {
    d_SLICE[ 32*gridDim.x*(bidy*32+tidy*2+0) + bidx*32 + tidx*2 + 0     ] = res0;
    d_SLICE[ 32*gridDim.x*(bidy*32+tidy*2+1) + bidx*32 + tidx*2 + 0     ] = res1;
    d_SLICE[ 32*gridDim.x*(bidy*32+tidy*2+0) + bidx*32 + tidx*2 + 1     ] = res2;
    d_SLICE[ 32*gridDim.x*(bidy*32+tidy*2+1) + bidx*32 + tidx*2 + 1     ] = res3;
  }
}


 __global__ static void    gputomo_kernel32_fan_heli_2by2(int num_proj,int num_bins, float axis_position,
						      float2 *d_SLICE,
						      float gpu_offset_x, float gpu_offset_y   , float * d_cos_s ,
						      float * d_sin_s ,float *  d_axis_s,
						      float dive,
						      float source_x,
						      int2 inizio2, int2 fine2,
						      float2 fattore_estremi2,
						      int ipoffset
						      ) {
  const int tidx = threadIdx.x;
  const int bidx = blockIdx.x;
  const int tidy = threadIdx.y;
  const int bidy = blockIdx.y;

  __shared__  float   shared[768] ;
  float  * sh_sin  = shared;
  float  * sh_cos  = shared+256;
  float  * sh_axis = sh_cos+256;

  float pcos, psin;
  float h0, h1, h2, h3;
  //  l'asse e' dato a partire dall' angolo  primo pixel, ma i centri dei pixel sono nel mezzo
  const float apos_off_x= gpu_offset_x   -  axis_position ;
  const float apos_off_y= gpu_offset_y   -  axis_position ;
  float acorr05;
  float2 res0, res1, res2, res3;
  float2 tmp_f2;
  float tmp0[2];
  float tmp1[2];
  float tmp2[2];
  float tmp3[2];

  float Vres0[2], Vres1[2], Vres2[2], Vres3[2];

  Vres0[0 ]=0;
  Vres1[0 ]=0;
  Vres2[0 ]=0;
  Vres3[0 ]=0;
  Vres0[1 ]=0;
  Vres1[1 ]=0;
  Vres2[1 ]=0;
  Vres3[1 ]=0;
  
  int fattore_estremi[2];
  fattore_estremi[0] = fattore_estremi2.x;
  fattore_estremi[1] = fattore_estremi2.y;

  int inizio[2];
  inizio[0] = inizio2.x;
  inizio[1] = inizio2.y;
  
  int fine[2];
  fine[0] = fine2.x;
  fine[1] = fine2.y;

  int myinizio = min(inizio[0], inizio[1] ) ; 
  int myfine   = min(fine  [0], fine  [1] ) ;
  

  const float bx00 = (32 * bidx  + 2 * tidx   + 0 +   apos_off_x  ) ;
  const float by00 = (32 * bidy  + 2 * tidy   + 0 +   apos_off_y  ) ;

  int lettofino=ipoffset+myinizio;  // letto= read   fino=till
  for(int proje=ipoffset+myinizio; proje<ipoffset+myfine; proje++) {
    if(proje>=lettofino) {
      __syncthreads();
      int ip = tidy*16+tidx;
      if( lettofino+ip < num_proj) {
	sh_cos [ip] = d_cos_s[lettofino+ip] ; // populating the shared memory
	sh_sin [ip] = d_sin_s[lettofino+ip] ;
	sh_axis[ip] = d_axis_s[lettofino+ip] ;
      }
      lettofino=lettofino+256; // 256=16*16 block size
      __syncthreads();
    }
    pcos = sh_cos[ -(lettofino-256)+ proje] ;
    psin = sh_sin[-(lettofino-256)+ proje] ;

    acorr05=   sh_axis[-(lettofino-256) +proje] ;

    h0 =  (acorr05+
	   bx00*pcos -
	   by00*psin
	   ) ;

    //     float corre = (-(h0  -source_x )     )*( bx00*psin +by00*pcos  )*dive;
    float corre = (h0  -source_x )*(1.0f/dive/(1.0f/dive +  bx00*psin +by00*pcos  )-1);


    h0 +=  corre ;

    h1 =  (acorr05+
	   (bx00+0)*pcos -
	   (by00+1)*psin
	   )+corre  ;
    h2 =  (acorr05+
	   (bx00+1)*pcos -
	   (by00+0)*psin
	   ) +corre ;
    h3 =  (acorr05+
	   (bx00+1)*pcos -
	   (by00+1)*psin
	   ) +corre ;

    tmp_f2 = tex2D(texcProjes,h0 +0.5f,proje-ipoffset  +0.5f );
    tmp0[0] = tmp_f2.x ;
    tmp0[1] = tmp_f2.y;
    
    tmp_f2 = tex2D(texcProjes,h1 +0.5f,proje-ipoffset  +0.5f );
    tmp1[0] = tmp_f2.x ;
    tmp1[1] = tmp_f2.y;
    
    tmp_f2 = tex2D(texcProjes,h2 +0.5f,proje-ipoffset  +0.5f );
    tmp2[0] = tmp_f2.x ;
    tmp2[1] = tmp_f2.y;
    
    tmp_f2 = tex2D(texcProjes,h3 +0.5f,proje-ipoffset  +0.5f );
    tmp3[0] = tmp_f2.x ;
    tmp3[1] = tmp_f2.y;
    
    for(int ks = 0; ks<2; ks++) {
      if(proje==ipoffset+inizio[ks] || proje==(ipoffset+fine[ks]-1) ) {
	
	if(h0>=0 && h0<num_bins) 
	  Vres0[ks]=Vres0[ks]+tmp0[ks]*fattore_estremi[ks] ;
	
	if(h1>=0 && h1<num_bins)
	  Vres1[ks]=Vres1[ks]+tmp1[ks]*fattore_estremi[ks] ;
	
	if(h2>=0 && h2<num_bins)
	  Vres2[ks]=Vres2[ks]+tmp2[ks]*fattore_estremi[ks] ;
	
	if(h3>=0 && h3<num_bins)
	  Vres3[ks]=Vres3[ks]+tmp3[ks]*fattore_estremi[ks] ;
	
      } else {
	if(proje>ipoffset+inizio[ks] && proje < (ipoffset+fine[ks]-1) ) {
	  
	  if(h0>=0 && h0<num_bins)
	    Vres0[ks]=Vres0[ks]+tmp0[ks] ;
	  if(h1>=0 && h1<num_bins)
	    Vres1[ks]=Vres1[ks]+tmp1[ks] ;
	  if(h2>=0 && h2<num_bins)
	    Vres2[ks]=Vres2[ks]+tmp2[ks] ;
	  if(h3>=0 && h3<num_bins)
	    Vres3[ks]=Vres3[ks]+tmp3[ks] ;
	}
      }
    }   
  };
  
  res0.x = Vres0[0];
  res1.x = Vres1[0];
  res2.x = Vres2[0];
  res3.x = Vres3[0];
  res0.y = Vres0[1];
  res1.y = Vres1[1];
  res2.y = Vres2[1];
  res3.y = Vres3[1];
  
  d_SLICE[ 32*gridDim.x*(bidy*32+tidy*2+0) + bidx*32 + tidx*2 + 0     ] = res0;
  d_SLICE[ 32*gridDim.x*(bidy*32+tidy*2+1) + bidx*32 + tidx*2 + 0     ] = res1;
  d_SLICE[ 32*gridDim.x*(bidy*32+tidy*2+0) + bidx*32 + tidx*2 + 1     ] = res2;
  d_SLICE[ 32*gridDim.x*(bidy*32+tidy*2+1) + bidx*32 + tidx*2 + 1     ] = res3;

}





__global__ static void    gputomo_kernel32_fan(int num_proj,int num_bins, float axis_position,
					       float *d_SLICE,
					       float gpu_offset_x, float gpu_offset_y   , float * d_cos_s ,
					       float * d_sin_s ,float *  d_axis_s,
					       float dive,
					       float source_x
					       ) {
  const int tidx = threadIdx.x;
  const int bidx = blockIdx.x;
  const int tidy = threadIdx.y;
  const int bidy = blockIdx.y;

  __shared__  float   shared[768] ;
  float  * sh_sin  = shared;
  float  * sh_cos  = shared+256;
  float  * sh_axis = sh_cos+256;

  float pcos, psin;
  float h0, h1, h2, h3;
  //  l'asse e' dato a partire dall' angolo  primo pixel, ma i centri dei pixel sono nel mezzo
  const float apos_off_x= gpu_offset_x   -  axis_position ;
  const float apos_off_y= gpu_offset_y   -  axis_position ;
  float acorr05;
  float res0, res1, res2, res3;

  res0=0;
  res1=0;
  res2=0;
  res3=0;

  const float bx00 = (32 * bidx  + 2 * tidx   + 0 +   apos_off_x  ) ;
  const float by00 = (32 * bidy  + 2 * tidy   + 0 +   apos_off_y  ) ;

  int lettofino=0;  // letto= read   fino=till
  for(int proje=0; proje<num_proj; proje++) {
    if(proje>=lettofino) {
      __syncthreads();
      int ip = tidy*16+tidx;
      if( lettofino+ip < num_proj) {
	sh_cos [ip] = d_cos_s[lettofino+ip] ; // populating the shared memory
	sh_sin [ip] = d_sin_s[lettofino+ip] ;
	sh_axis[ip] = d_axis_s[lettofino+ip] ;
      }
      lettofino=lettofino+256; // 256=16*16 block size
      __syncthreads();
    }
    pcos = sh_cos[ -(lettofino-256)+ proje] ;
    psin = sh_sin[-(lettofino-256)+ proje] ;

    acorr05=   sh_axis[-(lettofino-256) +proje] ;

    h0 =  (acorr05+
	   bx00*pcos -
	   by00*psin
	   ) ;

    //     float corre = (-(h0  -source_x )     )*( bx00*psin +by00*pcos  )*dive;
    float corre = (h0  -source_x )*(1.0f/dive/(1.0f/dive +  bx00*psin +by00*pcos  )-1);


    h0 +=  corre ;

    h1 =  (acorr05+
	   (bx00+0)*pcos -
	   (by00+1)*psin
	   )+corre  ;
    h2 =  (acorr05+
	   (bx00+1)*pcos -
	   (by00+0)*psin
	   ) +corre ;
    h3 =  (acorr05+
	   (bx00+1)*pcos -
	   (by00+1)*psin
	   ) +corre ;


    if(h0>=0 && h0<num_bins)
      res0=res0+tex2D(texProjes,h0 +0.5f,proje  +0.5f ) ;

    if(h1>=0 && h1<num_bins)
      res1=res1+tex2D(texProjes,h1 +0.5f,proje  +0.5f ) ;

    if(h2>=0 && h2<num_bins)
      res2=res2+tex2D(texProjes,h2 +0.5f,proje  +0.5f ) ;

    if(h3>=0 && h3<num_bins)
      res3=res3+tex2D(texProjes,h3 +0.5f,proje  +0.5f ) ;
  };
  if(1) {
    d_SLICE[ 32*gridDim.x*(bidy*32+tidy*2+0) + bidx*32 + tidx*2 + 0     ] = res0;
    d_SLICE[ 32*gridDim.x*(bidy*32+tidy*2+1) + bidx*32 + tidx*2 + 0     ] = res1;
    d_SLICE[ 32*gridDim.x*(bidy*32+tidy*2+0) + bidx*32 + tidx*2 + 1     ] = res2;
    d_SLICE[ 32*gridDim.x*(bidy*32+tidy*2+1) + bidx*32 + tidx*2 + 1     ] = res3;
  }
}




__global__ static void    gputomo_kernel32_fan_2by2(int num_proj,int num_bins, float axis_position,
					       float2 *d_SLICE,
						    float gpu_offset_x, float gpu_offset_y   , float * d_cos_s ,
						    float * d_sin_s ,float *  d_axis_s,
						    float dive,
						    float source_x
						    ) {
  const int tidx = threadIdx.x;
  const int bidx = blockIdx.x;
  const int tidy = threadIdx.y;
  const int bidy = blockIdx.y;

  __shared__  float   shared[768] ;
  float  * sh_sin  = shared;
  float  * sh_cos  = shared+256;
  float  * sh_axis = sh_cos+256;

  float pcos, psin;
  float h0, h1, h2, h3;
  //  l'asse e' dato a partire dall' angolo  primo pixel, ma i centri dei pixel sono nel mezzo
  const float apos_off_x= gpu_offset_x   -  axis_position ;
  const float apos_off_y= gpu_offset_y   -  axis_position ;
  float acorr05;
  float2 res0, res1, res2, res3;

  res0=(float2){0.0f, 0.0f};
  res1=(float2){0.0f, 0.0f};
  res2=(float2){0.0f, 0.0f};
  res3=(float2){0.0f, 0.0f};

  const float bx00 = (32 * bidx  + 2 * tidx   + 0 +   apos_off_x  ) ;
  const float by00 = (32 * bidy  + 2 * tidy   + 0 +   apos_off_y  ) ;

  int lettofino=0;  // letto= read   fino=till
  for(int proje=0; proje<num_proj; proje++) {
    if(proje>=lettofino) {
      __syncthreads();
      int ip = tidy*16+tidx;
      if( lettofino+ip < num_proj) {
	sh_cos [ip] = d_cos_s[lettofino+ip] ; // populating the shared memory
	sh_sin [ip] = d_sin_s[lettofino+ip] ;
	sh_axis[ip] = d_axis_s[lettofino+ip] ;
      }
      lettofino=lettofino+256; // 256=16*16 block size
      __syncthreads();
    }
    pcos = sh_cos[ -(lettofino-256)+ proje] ;
    psin = sh_sin[-(lettofino-256)+ proje] ;

    acorr05=   sh_axis[-(lettofino-256) +proje] ;

    h0 =  (acorr05+
	   bx00*pcos -
	   by00*psin
	   ) ;

    //     float corre = (-(h0  -source_x )     )*( bx00*psin +by00*pcos  )*dive;
    float corre = (h0  -source_x )*(1.0f/dive/(1.0f/dive +  bx00*psin +by00*pcos  )-1);


    h0 +=  corre ;

    h1 =  (acorr05+
	   (bx00+0)*pcos -
	   (by00+1)*psin
	   )+corre  ;
    h2 =  (acorr05+
	   (bx00+1)*pcos -
	   (by00+0)*psin
	   ) +corre ;
    h3 =  (acorr05+
	   (bx00+1)*pcos -
	   (by00+1)*psin
	   ) +corre ;


    if(h0>=0 && h0<num_bins)
      res0+=tex2D(texcProjes,h0 +0.5f,proje  +0.5f ) ;

    if(h1>=0 && h1<num_bins)
      res1+=tex2D(texcProjes,h1 +0.5f,proje  +0.5f ) ;

    if(h2>=0 && h2<num_bins)
      res2+=tex2D(texcProjes,h2 +0.5f,proje  +0.5f ) ;

    if(h3>=0 && h3<num_bins)
      res3+=tex2D(texcProjes,h3 +0.5f,proje  +0.5f ) ;
  };
  if(1) {
    d_SLICE[ 32*gridDim.x*(bidy*32+tidy*2+0) + bidx*32 + tidx*2 + 0     ] = res0;
    d_SLICE[ 32*gridDim.x*(bidy*32+tidy*2+1) + bidx*32 + tidx*2 + 0     ] = res1;
    d_SLICE[ 32*gridDim.x*(bidy*32+tidy*2+0) + bidx*32 + tidx*2 + 1     ] = res2;
    d_SLICE[ 32*gridDim.x*(bidy*32+tidy*2+1) + bidx*32 + tidx*2 + 1     ] = res3;
  }
}



// ---------------------------------------


/*
  __global__ static void    gputomo_kernel(int num_proj,int num_bins, float axis_position,
  float *d_SLICE,
  float gpu_offset_x, float gpu_offset_y , float * d_cos_s , float * d_sin_s ,float *  d_axis_s
  ) {
  const int tidx = threadIdx.x;
  const int bidx = blockIdx.x;
  const int tidy = threadIdx.y;
  const int bidy = blockIdx.y;

  __shared__  float   shared[768] ;
  float  * sh_sin  = shared;
  float  * sh_cos  = shared+256;
  float  * sh_axis = sh_cos+256;

  float pcos, psin;
  float h;
  //  l'asse e' dato a partire dall' angolo  primo pixel, ma i centri dei pixel sono nel mezzo
  const float apos_off_x= gpu_offset_x   -  axis_position ;
  const float apos_off_y= gpu_offset_y   -  axis_position ;
  float acorr05;
  float res;

  res=0;

  const float bx = (16* bidx  + tidx    +   apos_off_x  ) ;
  const float by = (16* bidy  + tidy    +   apos_off_y  ) ;

  int lettofino=0;
  for(int proje=0; proje<num_proj; proje++) {
  if(proje>=lettofino) {
  syncthreads();
  int ip = tidy*16+tidx;
  if( lettofino+ip < num_proj) {
  sh_cos [ip] = d_cos_s[lettofino+ip] ;
  sh_sin [ip] = d_sin_s[lettofino+ip] ;
  sh_axis[ip] = d_axis_s[lettofino+ip] ;
  }
  lettofino=lettofino+256;
  syncthreads();
  }
  pcos = sh_cos[ -(lettofino-256)+ proje] ;
  psin = sh_sin[-(lettofino-256)+ proje] ;

  acorr05=   sh_axis[-(lettofino-256) +proje] ;

  h =  (acorr05+
  bx*pcos -
  by*psin
  ) ;
  if(h>=0 && h<num_bins)
  res=res+tex2D(texProjes,h +0.5f,proje  +0.5f ) ;

  };

  d_SLICE[ 16*gridDim.x*(bidy*16+tidy) + bidx*16 + tidx     ] = res;
  }
*/
// ---------------------------------------


// extern "C" {
//   int gpu_mainInit(Gpu_Context * self, float *filter );
//   int gpu_main(Gpu_Context * self, float *WORK , float * SLICE);
// }

//Round a / b to nearest higher integer value
int iDivUp(int a, int b){
  return (a % b != 0) ? (a / b + 1) : (a / b);
}

//Align a to nearest higher multiple of b
int iAlignUp(int a, int b){
  return (a % b != 0) ?  (a - a % b + b) : a;
}




#define blsize_cufft 32
__global__   void kern_cufft_filter_talbot(int Hfft_size,cufftReal *dev_iWork,  int batch_size, int dim_fft){
  int gid;
  float tmp0,tmp1, fact;
  gid = threadIdx.x + blockIdx.x*blockDim.x;
  if(gid<Hfft_size) {
    if(gid==0) {
      fact=0.0f;
    } else  {
      fact= 1.0f/ (gid*M_PI*2.0f*2.0f/dim_fft);
    }
    for(int i=0;i<batch_size;i++){
      tmp0 =  dev_iWork[2*gid   + i*(2*Hfft_size)];
      tmp1 =  dev_iWork[2*gid+1 + i*(2*Hfft_size)];
      dev_iWork[2*gid   + i*(2*Hfft_size)] = +tmp1*fact/dim_fft ;
      dev_iWork[2*gid+1 + i*(2*Hfft_size)] = -tmp0*fact/dim_fft ;
    }
  }
  __syncthreads();
}



#define blsize_cufft 32
__global__   void kern_cufft_filter_talbot_2by2(int Hfft_size,cufftReal *dev_iWork,  int batch_size, int dim_fft){
  int gid;
  float tmp0,tmp1, fact;
  gid = threadIdx.x + blockIdx.x*blockDim.x;
  if(gid<Hfft_size) {
    if(gid==0) {
      fact=0.0f;
    } else  {
      if(  gid <= Hfft_size/2   ) {
	fact= 1.0f/ (gid*M_PI*2.0f*2.0f/dim_fft);
      } else {
	fact= -1.0f/ (( Hfft_size - gid ) *M_PI*2.0f*2.0f/dim_fft );
      }
    }
    for(int i=0;i<batch_size;i++){
      tmp0 =  dev_iWork[2*gid   + i*(2*Hfft_size)];
      tmp1 =  dev_iWork[2*gid+1 + i*(2*Hfft_size)];
      dev_iWork[2*gid   + i*(2*Hfft_size)] = +tmp1*fact/dim_fft ;
      dev_iWork[2*gid+1 + i*(2*Hfft_size)] = -tmp0*fact/dim_fft ;
    }
  }
  __syncthreads();
}

#define blsize_cufft 32
__global__   void kern_cufft_filter(int Hfft_size,cufftReal *dev_iWork, float *dev_Filter, int batch_size, int dim_fft){
  int gid;
  float filter;
  
  gid = threadIdx.x + blockIdx.x*blockDim.x;
  
  if(gid<Hfft_size) {
    
    filter=dev_Filter[gid]/dim_fft;;

 
    
    for(int i=0;i<batch_size;i++){
      dev_iWork[2*gid   + i*(2*Hfft_size)] *= filter;
      dev_iWork[2*gid+1 + i*(2*Hfft_size)] *= filter;
      // dev_iWork[2*gid + i*(2*Hfft_size)]  = dev_iWork[2*gid + i*(2*Hfft_size)]/dim_fft;
      // dev_iWork[2*gid+1 + i*(2*Hfft_size)]  = dev_iWork[2*gid+1 + i*(2*Hfft_size)]/dim_fft;
    }
  }
  __syncthreads();
}

// Angle-dependent filtering (used for SIRT-Filter)
__global__ void kern_cufft_filter_angledependent(
    int Hfft_size,
    cufftReal *dev_iWork,
    float *dev_Filter,
    int batch_size,
    int dim_fft,
    int iproj) // projection number : 0, fftbunch, 2*fftbunch, ...
{
  int gidx = threadIdx.x + blockIdx.x*blockDim.x;
  int gidy = threadIdx.y + blockIdx.y*blockDim.y;
  if(gidx < Hfft_size && gidy < batch_size) {
    int filter_idx = (iproj + gidy)*dim_fft + gidx;
    float filter_val = dev_Filter[filter_idx]/dim_fft;
    dev_iWork[2*gidx   + gidy*(2*Hfft_size)] *= filter_val;
    dev_iWork[2*gidx+1 + gidy*(2*Hfft_size)] *= filter_val;
  }
  __syncthreads();
}












__global__   void kern_cufft_filter_corrpersymm(int Hfft_size,cufftReal *dev_iWork, int batch_size, int dim_fft){
  int gid;
  gid = threadIdx.x + blockIdx.x*blockDim.x;
  if(gid<Hfft_size) {
    if(gid) {
      for(int i=0;i<batch_size;i++){
	dev_iWork[2*gid   + i*(2*Hfft_size)] *= 2;
	dev_iWork[2*gid+1 + i*(2*Hfft_size)] *= 2;
      }
    }
  }
  __syncthreads();
}


__global__   void kern_recopy(int num_bins,int dim_fft,int batch_size, float *dev_Work_perproje,  float *dev_rWork
			      )  {
  int gid;
  gid = threadIdx.x + blockIdx.x*blockDim.x;
  if(gid<num_bins) {
    for(int i=0;i<batch_size;i++){
      dev_Work_perproje[ gid + i*num_bins ] = dev_rWork[ gid + i*(dim_fft) ] ;
    }
  }
  __syncthreads();
}

__global__   void kern_filter_nans(int num_bins,int batch_size,  float *dev_rWork
				   )  {
  int gid;
  gid = threadIdx.x + blockIdx.x*blockDim.x;
  if(gid<num_bins) {
    for(int i=0;i<batch_size;i++){
      if( dev_rWork[ gid + i*num_bins ] != dev_rWork[ gid + i*num_bins ])
	dev_rWork[ gid + i*num_bins ] = 0 ; 
    }
  }
  __syncthreads();
}





__global__ static void    slicerot_kernel( int dimslice,
					   float *d_SLICE,
					   float dangle,
					   int duty_oversampling) {

  const int tidx = threadIdx.x;
  const int bidx = blockIdx.x;
  const int tidy = threadIdx.y;
  const int bidy = blockIdx.y;

  const float axis_position =  (dimslice-1 )/ 2.0f  ;
  const float rx = (16* bidx  + tidx     ) - axis_position;
  const float ry = (16* bidy  + tidy     ) - axis_position;

  const float anglestep     =  dangle/(duty_oversampling)        ;
  const float angleshift    = -anglestep*(duty_oversampling-1)/2.0f ;

  float res=0;
  float rotangle;
  float x,y;
  for(int irot=0; irot<duty_oversampling ; irot++) {
    rotangle =  angleshift + irot *   anglestep  ;
    float cc = cos(rotangle), ss = sin(rotangle)  ;
    x   =   rx*cc  -  ry*ss  +   axis_position   ;
    y   =   ry*cc  +  rx*ss  +   axis_position   ;
    res =   res + tex2D(texProjes ,x + 1.5f, y  +1.5f ) ;
  }
  d_SLICE[ 16*gridDim.x*(bidy*16+tidy) + bidx*16 + tidx     ] = res/duty_oversampling  ;
}


__global__   void kern_recopy_slope(int num_bins,int dim_fft,int batch_size, float *dev_Work_perproje,  float *dev_rWork,
				    float overlapping, float pente_zone, float flat_zone,
				    float *d_axis_s,
				    float prof_shift, float prof_fact)  {
  int gid;
  float dx, peso, ax_pos_corr ;
  gid = threadIdx.x + blockIdx.x*blockDim.x;
  if(gid<num_bins) {
    for(int i=0;i<batch_size;i++){
      ax_pos_corr = d_axis_s[ i ];
      if(  fabs(gid-  ax_pos_corr ) <= overlapping ) {
	if(  fabs(gid- ax_pos_corr ) <= flat_zone) {
	  dev_Work_perproje[ gid + i*num_bins ]=  dev_rWork[ gid + i*(dim_fft) ];
	} else {
	  if( gid> ax_pos_corr  ) {
	    dx = ax_pos_corr  - gid + flat_zone;
	  } else {
	    dx = ax_pos_corr  - gid - flat_zone;
	  }
	  peso = prof_shift + prof_fact * (0.5*(1.0+ (        dx      )/pente_zone));
	  dev_Work_perproje[ gid + i*num_bins ] = peso*dev_rWork[ gid + i*(dim_fft) ]* 2;
	}
      } else if (gid>ax_pos_corr ) {
	dev_Work_perproje[ gid + i*num_bins ] = prof_shift  * dev_rWork[ gid + i*(dim_fft) ]* 2;
      } else {
	dev_Work_perproje[ gid + i*num_bins ] = (prof_shift + prof_fact*1.0)  *   dev_rWork[ gid + i*(dim_fft) ]  * 2;
      }
      __syncthreads();
    }
  }
  //  __syncthreads();
}

__global__   void kern_recopy_slope_2by2(int num_bins,int dim_fft,int batch_size, float *dev_Work_perproje,  float *dev_rWork,
				    float overlapping, float pente_zone, float flat_zone,
				    float *d_axis_s,
				    float prof_shift, float prof_fact)  {
  int gid;
  float dx, peso, ax_pos_corr ;
  gid = threadIdx.x + blockIdx.x*blockDim.x;
  if(gid<num_bins) {
    for(int i=0;i<batch_size;i++){
      ax_pos_corr = d_axis_s[ i ] * 2;
      if(  fabs(gid-  ax_pos_corr ) <= overlapping ) {
	if(  fabs(gid- ax_pos_corr ) <= flat_zone) {
	  dev_Work_perproje[ gid + i*num_bins ]=  dev_rWork[ gid + i*(dim_fft) ];
	} else {
	  if( gid> ax_pos_corr  ) {
	    dx = ax_pos_corr  - gid + flat_zone;
	  } else {
	    dx = ax_pos_corr  - gid - flat_zone;
	  }
	  peso = prof_shift + prof_fact * (0.5*(1.0+ (        dx      )/pente_zone));
	  dev_Work_perproje[ gid + i*num_bins ] = peso*dev_rWork[ gid + i*(dim_fft) ]* 2;
	}
      } else if (gid>ax_pos_corr ) {
	dev_Work_perproje[ gid + i*num_bins ] = prof_shift  * dev_rWork[ gid + i*(dim_fft) ]* 2;
      } else {
	dev_Work_perproje[ gid + i*num_bins ] = (prof_shift + prof_fact*1.0)  *   dev_rWork[ gid + i*(dim_fft) ]  * 2;
      }
      __syncthreads();
    }
  }
  //  __syncthreads();
}





__global__   void kern_recopy_slope_leastsquare(int num_bins,int dim_fft,int batch_size, float *dev_Work_perproje,  float *dev_rWork,
						float overlapping, float pente_zone, float flat_zone,
						float *d_axis_s,
						float prof_shift, float prof_fact)  {
  int gid;
  float   ax_pos_corr ;
  // float dx, peso; 
  gid = threadIdx.x + blockIdx.x*blockDim.x;
  if(gid<num_bins) {
    for(int i=0;i<batch_size;i++){
      ax_pos_corr = d_axis_s[ i ];
      if(  fabs(gid-  ax_pos_corr ) <= overlapping ) {
	if(  fabs(gid- ax_pos_corr ) <= flat_zone) {
	  dev_Work_perproje[ gid + i*num_bins ]=  dev_rWork[ gid + i*(dim_fft) ];
	} else {
	  if( gid> ax_pos_corr  ) {
	    // dx = ax_pos_corr  - gid + flat_zone;
	  } else {
	    // dx = ax_pos_corr  - gid - flat_zone;
	  }
	  // peso = prof_shift + prof_fact * (0.5*(1.0+ (        dx      )/pente_zone));
	  // dev_Work_perproje[ gid + i*num_bins ] = peso*dev_rWork[ gid + i*(dim_fft) ];
	  dev_Work_perproje[ gid + i*num_bins ] = dev_rWork[ gid + i*(dim_fft) ];
	}
      } else if (gid>ax_pos_corr ) {
	// dev_Work_perproje[ gid + i*num_bins ] = prof_shift  * dev_rWork[ gid + i*(dim_fft) ];
	dev_Work_perproje[ gid + i*num_bins ] =  dev_rWork[ gid + i*(dim_fft) ];
      } else {
	// dev_Work_perproje[ gid + i*num_bins ] = (prof_shift + prof_fact*1.0)  *   dev_rWork[ gid + i*(dim_fft) ] ;
	dev_Work_perproje[ gid + i*num_bins ] =   dev_rWork[ gid + i*(dim_fft) ] ;
      }
      __syncthreads();
    }
  }
  //  __syncthreads();
}



__global__ void  padda_kernel(float * d_r_sino_error,int num_bins,int  np2,int  numpjs     )   {
  int gidx = threadIdx.x + blockIdx.x*blockDim.x;
  int gidy = threadIdx.y + blockIdx.y*blockDim.y;
  if (gidx < np2-num_bins && gidy < numpjs) {
    d_r_sino_error[ gidy*np2 + (num_bins+gidx) ] =  d_r_sino_error[ gidy*np2 + (num_bins-1)*( gidx>(np2-num_bins)/2 )];
  }
}

__global__ void  transition_kernel(float * d_r_sino_error,int num_bins,int  np2,int  numpjs , float axis_position    )   {
  int gidx = threadIdx.x + blockIdx.x*blockDim.x;
  int gidy = threadIdx.y + blockIdx.y*blockDim.y;
  float x ;
  if( gidy < numpjs && gidx < np2) {
    if ( 2*axis_position > num_bins) {
      if(gidx > axis_position && gidx<= num_bins  )
	{
	  x = ( num_bins -gidx   )/( num_bins-axis_position ) ;
	  d_r_sino_error[ gidy*np2 + (gidx) ] *=  sqrt(     0.5* x*x*x*(x*(x*6 - 15) + 10) ) ;
	}  else  if(gidx > 2*axis_position-num_bins    && gidx<= num_bins  )
	{
	  x = -(2*axis_position-num_bins -gidx   )/( num_bins-axis_position ) ;
	  d_r_sino_error[ gidy*np2 + (gidx) ] *=  sqrt(   1 -  0.5* x*x*x*(x*(x*6 - 15) + 10) ) ;
	} 
    }
    if ( 2*axis_position < num_bins) {
      if(gidx < axis_position )
	{
	  x =  ( gidx   )/(axis_position ) ; 
	  d_r_sino_error[ gidy*np2 + (gidx) ] *=  sqrt(0.5*x*x*x*(x*(x*6 - 15) + 10)) ;
	}  else if (gidx < 2*axis_position )
	{
	  x =  ( 2*axis_position-gidx   )/(axis_position ) ; 
	  d_r_sino_error[ gidy*np2 + (gidx) ] *=  sqrt(1-0.5*x*x*x*(x*(x*6 - 15) + 10)) ;
	} 
    }
  }
}


__global__   void     kern_mult( cuFloatComplex  * d_fftwork, cuFloatComplex * d_kernelbuffer, int dimbuff ) {
  int gid;
  gid = threadIdx.x + blockIdx.x*blockDim.x;
  if(gid<dimbuff) {
    d_fftwork[gid]=cuCmulf(d_fftwork[gid],d_kernelbuffer[gid]) ;
  }
}





/**
 * Calculates value of sinc function
 *
 * @param x is value at an normalized from PI to -PI interval
 * @result value of sinc function
 */
static float dfi_cuda_sinc(float x) {
  return (x == 0.0f) ? 1.0 : sin(M_PI * x)/(M_PI * x);
}

/**
 * Calculates the value of Hamming window function
 *
 * @param i is a number of current sample
 * @param length is the length of Hamming window
 * @result value of Hamming window function
 */
static float dfi_cuda_hamming_window(float i, float length) {
  return (0.54f - 0.46f * cos(2*M_PI*((float)i/(float)length) ) );
}

/**
 * Calculates the values of presampled values array
 *
 * @param length is the length of presampled array
 * @result presampled kernel values
 */
static float *dfi_cuda_get_ktbl(int length)
{
  float *ktbl = (float *)malloc(length * sizeof(float));

  if (!length%2) {
    printf("Error: Length of ktbl cannot be even!\n");
    exit(1);
  }

  int ktbl_len2 = (length - 1)/2;
  float step = M_PI/(float)ktbl_len2;

  float value = -ktbl_len2 * step;

  for (int i = 0; i < length; ++i, value += step) {
    ktbl[i] = dfi_cuda_sinc(value) * dfi_cuda_hamming_window(i, length);
  }

  return ktbl;
}

int dfi_cuda_calc_blocks(int items, int block_size, int *increment) {
  int grids = items / block_size;
  int inc = grids * block_size - items;

  if (inc) {
    if (increment) *increment = inc + block_size;
    return grids + 1;
  }

  if (increment) *increment = inc;
  return grids;
}


//[PP.add]
#define MDEBUG 1
#ifdef MDEBUG
/*
 * Debug purpose routines. Use Thrust if they take too much space
 */
int print_device_array(float* d_array, int numels, char* format) {
  float* h_array = new float[numels];
  CUDA_SAFE_CALL(cudaMemcpy(h_array,d_array, numels*sizeof(float), cudaMemcpyDeviceToHost));
  for (int i=0; i < numels; i++) {
    printf(format,h_array[i]);
  }
  delete[] h_array;
  return 0;
}
__global__ void kern_convert_float2_to_float(float2* inArray, float* outArray, int numels) {
  int gid = threadIdx.x + blockIdx.x*blockDim.x;
  if(gid<numels) {
    outArray[gid] = inArray[gid].x;
    outArray[numels+gid] = inArray[gid].y;
  }
}
int print_device_float2_array(float2* d_array, int numels, char* format) {
  float* h_array = new float[2*numels];
  float* d_array_f;
  CUDA_SAFE_CALL(cudaMalloc(&d_array_f, 2*numels*sizeof(float)));
  dim3 blk, grd;
  blk = dim3( blsize_cufft , 1 , 1 );
  grd = dim3( iDivUp(numels ,blsize_cufft) , 1 , 1 );
  kern_convert_float2_to_float<<<grd,blk>>> (d_array,d_array_f,numels);
  CUDA_SAFE_CALL(cudaMemcpy(h_array,d_array_f, 2*numels*sizeof(float), cudaMemcpyDeviceToHost));
  for (int i=0; i < 2*numels; i++) {
    printf(format,h_array[i]);
  }
  delete[] h_array;
  CUDA_SAFE_CALL(cudaFree(d_array_f));
  return 0;
}

int print_device_realpart(float2* d_array, int numels, char* format) {

  float2* h_array = (float2*) malloc(numels*sizeof(float2));
  CUDA_SAFE_CALL(cudaMemcpy(h_array,d_array, numels*sizeof(float), cudaMemcpyDeviceToHost));
  for (int i=0; i < numels; i++) {
    printf(format,h_array[i].x);
  }
  delete[] h_array;
  return 0;
}

//elapsed time in ms
double elapsed(clock_t start) {
  return (double) (clock()-start) / ((double)CLOCKS_PER_SEC)*1000.0;
}



#endif

__global__  void kern_compute_discrete_ramp(int length, cufftReal* oArray) {
  int gid = threadIdx.x + blockIdx.x*blockDim.x;
  if(gid<=length/2) {
    float val = ((gid & 1) ? (-1.0f/M_PI/M_PI/gid/gid) : (0.0f));
    if (gid == 0) oArray[gid] = 0.25f;
    else if (gid == length/2) oArray[gid] = val;
    else {
      oArray[gid] = val;
      if(gid)
	oArray[length-gid] = val;
    }
  }
}

/**
 * @brief compute_discretized_ramp_filter
 * @param length : filter length
 * @param d_r : device array (real)
 * @param d_i : device array (complex)
 * @param myplan : plan for Fourier Transform
 */
cufftComplex* compute_discretized_ramp_filter(
					      int length,
					      cufftReal* d_r,
					      cufftComplex* d_i,
					      cufftHandle myplan)
{
  int hlen = length/2+1;
  dim3 blk, grd;
  blk = dim3( blsize_cufft , 1 , 1 );
  grd = dim3( iDivUp(length ,blsize_cufft) , 1 , 1 );
  kern_compute_discrete_ramp<<<grd,blk>>> (length, d_r);
  CUDA_SAFE_FFT(cufftExecR2C(myplan,(cufftReal *) d_r,(cufftComplex *) d_i));
  cufftComplex* filterCoefs;
  CUDA_SAFE_CALL(cudaMalloc(&filterCoefs, hlen*sizeof(cufftComplex)));
  CUDA_SAFE_CALL(cudaMemcpy(filterCoefs,d_i, hlen*sizeof(cufftComplex), cudaMemcpyDeviceToDevice));
  //print_device_array(d_r,length,"%.6f, ");
  //print_device_float2_array(d_i,hlen,"%.6f, ");
  //getchar();
  //puts("ok");
  return filterCoefs;
}

/**
   @brief In-place one-dimensional filtering by pointwise multiplication in Fourier domain. Supports batched FFT
   @param inArray : input array (float2*)
   @param filter : filter coefficients (float2*)
   @param length : array length
   @param sizeY : for batched FFT
*/
__global__ void kern_fourier_filter(cufftComplex* inArray, cufftComplex* filter, int sizeX, int sizeY) {
  int gidx = threadIdx.x + blockIdx.x*blockDim.x;
  int gidy = threadIdx.y + blockIdx.y*blockDim.y;
  if (gidx < sizeX && gidy < sizeY) {
    //cufftComplex tmp = inArray[gidy*sizeX+gidx];
    inArray[gidy*sizeX+gidx].x *= filter[gidx].x /(2*(sizeX-1)); //(tmp.x*filter[gidx].x - tmp.y*filter[gidx].y)/sizeX/2;
    inArray[gidy*sizeX+gidx].y *= filter[gidx].x /(2*(sizeX-1)); //(tmp.x*filter[gidx].y + tmp.y*filter[gidx].x)/sizeX/2;
  }
}

int debug_print = 0;//global variable for debug...


/**
 * @brief nextpow2 : nextpow2(2047) = 2048
 *        see https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
 */
int nextpow2(int v) {
  v--;
  v |= v >> 1;
  v |= v >> 2;
  v |= v >> 4;
  v |= v >> 8;
  v |= v >> 16;
  v++;
  return v;
}
int nextpow2_padded(int v) {
  int vold=v;
  v--;
  v |= v >> 1;
  v |= v >> 2;
  v |= v >> 4;
  v |= v >> 8;
  v |= v >> 16;
  v++;
  if(v<vold*1.5) v*=2;
  return v;
}
int ilog2(int i) {
  int l = 0;
  while (i >>= 1) { ++l; }
  return l;
}
int ispow2(int v) {
  return (v && !(v & (v - 1)));
}
int ipow(int a, int n) {
  if (n == 0) return 1;
  if (n == 1) return a;
  if (n & 1) return a*ipow(a*a,(n-1)/2);
  else return ipow(a*a,n/2);
}

/**
 *  Compute an interpolating wavelet  of length initial_length*(2**nref),
 *    and keeps only the right part of length 3*(2**nref)
 *    usage: compute_wavelet(initial_length, nref)
 */
float* wavelet_extend_double(float* A, int L) {
  float* B = (float*) calloc(2*L, sizeof(float));
  float coefs[4] = { 1.0,  9.0/16.0, 0.0,-1.0/16 };
  int ia, ib;
  for (ia=0, ib=0; ia < L && ib < 2*L ; ia++, ib+=2) {
    B[ib] +=  coefs[0] * A[ia];
    B[1+ib] += coefs[1] * A[ia];
  }
  for (ia=0, ib=3; ia < L-1 && ib < 2*L ; ia++, ib+=2) {
    B[ib] += coefs[3] * A[ia];
  }
  for (ia=1, ib=1; ia < L && ib < 2*L-2 ; ia++, ib+=2) {
    B[ib] += coefs[1] * A[ia];
  }
  for (ia=3, ib=3; ia < L && ib < 2*L-4 ; ia++, ib+=2) {
    B[ib] += coefs[3] * A[ia];
  }
  return B;
}

float* compute_wavelet(int L0, int nref, int nrings) {
  int center0 = L0/2;
  float* psi = (float*) calloc(L0, sizeof(float));
  psi[center0] = 1.0f;
  int L = L0; int i;
  float* psi2;
  for (i=0; i < nref; i++) {
    psi2 = wavelet_extend_double(psi, L);
    free(psi);
    psi = psi2;
    L *= 2;
  }
  //keep only the right part of the (symetric) wavelet
  int wstep = ipow(2,nref);
  int center = center0*wstep;
  float* res = (float*) calloc((nrings-1)*wstep,sizeof(float));
  memcpy(res, psi+center, (nrings-1)*wstep*sizeof(float));
  free(psi);
  return res; //Lr = 3*wstep+1+1//for rings
}


//[end of PP.add]







#define fftbunch 128

typedef struct {
  cudaStream_t  stream[2];
} Gpu_Streams ;



typedef struct {
  float sin_psi;
  float cos_psi;
  float sin_tilt;
  float cos_tilt;
 
} DectTilt ;


#define USE_R2C_TRANSFORM 1

#define BLOCK_SIZE 16
#define BLOCK_SIZE_X BLOCK_SIZE
#define BLOCK_SIZE_Y BLOCK_SIZE

texture<cufftComplex, 2, cudaReadModeElementType> dfi_tex_projections;
texture<float, 1, cudaReadModeElementType> dfi_tex_ktbl;
texture<float, 1, cudaReadModeElementType> dfp_tex_ktbl;

/// DFI kernels

__global__ static void  dfi_cuda_zeropadding_real(cufftReal *input, int sino_width, int dim2_fft, cufftReal *output) {

  const int dim_x = gridDim.x * blockDim.x;
  int idx = blockIdx.x * BLOCK_SIZE + threadIdx.x;
  int idy = blockIdx.y * BLOCK_SIZE + threadIdx.y;
  long out_idx = idy * dim2_fft + idx;

  int lpart = sino_width/2;
  int rpart = sino_width/2;
  int dim_x2 = dim_x/2;
  int len_to_lpart = dim_x - lpart;

  output[out_idx] = (idx < rpart) ? input[idy * sino_width + (lpart + idx)] :
    (idx < (dim_x2 + (dim_x2 - lpart))) ? 0.0f : input[idy * sino_width + (idx - len_to_lpart)];
}

__global__ static void  dfi_cuda_zeropadding_complex(cufftReal *input, int sino_width, cufftComplex *output) {

  const int dim_x = gridDim.x * blockDim.x;
  int idx = blockIdx.x * BLOCK_SIZE + threadIdx.x;
  int idy = blockIdx.y * BLOCK_SIZE + threadIdx.y;
  long out_idx = idy * dim_x + idx;

  int lpart = sino_width/2;
  int rpart = sino_width/2;
  int dim_x2 = dim_x/2;
  int len_to_lpart = dim_x - lpart;

  output[out_idx].x = (idx < rpart) ? input[idy * sino_width + (lpart + idx)] :
    (idx < (dim_x2 + (dim_x2 - lpart))) ? 0.0f :
    input[idy * sino_width + (idx - len_to_lpart)];
  output[out_idx].y = 0.0f;
}

// __global__ static void  dfi_cuda_interpolation_sinc_v2(float L2,
// 						       int ktbl_len2,
// 						       int center_shift,
// 						       float table_spacing,
// 						       float angle_step_rad,
// 						       float theta_len,
// 						       float rho_len,
// 						       float rho_len2,
// 						       float max_radius,
// 						       cufftComplex *output) {
//   //variables
//   float2 out_coord, norm_gl_coord, in_coord;
//   int iul, iuh, ivl, ivh,  i, j, k;
//   float res_real, res_imag, weight, kernel_x_val;
//   long out_idx;

//   res_real = 0.0f, res_imag = 0.0f;
//   out_idx = 0;

//   out_coord.x = (blockIdx.x * BLOCK_SIZE + threadIdx.x);
//   out_coord.y = (blockIdx.y * BLOCK_SIZE + threadIdx.y);

//   out_idx = out_coord.y * (int)rho_len + out_coord.x;

//   //calculate coordinates
//   norm_gl_coord.x = out_coord.x - center_shift;
//   norm_gl_coord.y = center_shift - out_coord.y;

//   float radius = sqrt(norm_gl_coord.x * norm_gl_coord.x + norm_gl_coord.y * norm_gl_coord.y);

//   if (radius <= max_radius) {
//     float theta = atan2(norm_gl_coord.y, norm_gl_coord.x);

//     in_coord.x = radius;
//     if (theta <= 0 and radius > 0) {
//       in_coord.x = rho_len - radius;
//     }

//     in_coord.y = (float) min(theta / angle_step_rad, theta_len - 1);

//     //sinc interpoaltion
//     iul = (int)ceil(in_coord.x - L2);
//     iul = (iul < 0) ? 0 : iul;

//     iuh = (int)floor(in_coord.x + L2);
//     iuh = (iuh > rho_len - 1) ? iuh = rho_len - 1 : iuh;

//     ivl = (int)ceil(in_coord.y - L2);
//     ivl = (ivl < 0) ? 0 : ivl;

//     ivh = (int)floor(in_coord.y + L2);
//     ivh = (ivh > theta_len - 1) ? ivh = theta_len - 1 : ivh;

//     float kernel_y[20];
//     for (i = ivl, j = 0; i <= ivh; ++i, ++j) {
//       kernel_y[j] = tex1D(dfi_tex_ktbl, ktbl_len2 + (in_coord.y - (float)i) * table_spacing);
//     }

//     for (i = iul; i <= iuh; ++i) {
//       kernel_x_val = tex1D(dfi_tex_ktbl, ktbl_len2 + (in_coord.x - (float)i) * table_spacing);

//       for (k = ivl, j = 0; k <= ivh; ++k, ++j) {
//         weight = kernel_y[j] * kernel_x_val;

// 	cufftComplex data_val = tex2D(dfi_tex_projections, (float)i, (float)k);

// 	res_real += data_val.x * weight;
// 	res_imag += data_val.y * weight;
//       }
//     }

//     output[out_idx].x = res_real;
//     output[out_idx].y = res_imag;
//   }
// }

__global__ static void  dfi_cuda_interpolation_sinc(int spectrum_offset,
						    float L2,
						    int ktbl_len2,
						    int raster_size,
						    int raster_size2,
						    float table_spacing,
						    float angle_step_rad,
						    float theta_max,
						    float rho_max,
						    int dim_x,
						    float max_radius,
						    cufftComplex *output) {
  //variables
  float2 out_coord, norm_gl_coord, in_coord;
  int iul, iuh, ivl, ivh, sign, i, j, k;
  float res_real, res_imag, weight, kernel_x_val;
  long out_idx;

  sign = 1;
  res_real = 0.0f, res_imag = 0.0f;
  out_idx = 0;

  out_coord.x = (blockIdx.x * BLOCK_SIZE + threadIdx.x);
  out_coord.y = (blockIdx.y * BLOCK_SIZE + threadIdx.y) + spectrum_offset;

  out_idx = out_coord.y * dim_x + out_coord.x;

  //calculate coordinates
  norm_gl_coord.x = out_coord.x - 1;
  norm_gl_coord.y = out_coord.y - raster_size2;

  float radius = sqrt(norm_gl_coord.x * norm_gl_coord.x + norm_gl_coord.y * norm_gl_coord.y);

  if (radius <= max_radius) {
    in_coord.y = atan2(norm_gl_coord.y,norm_gl_coord.x);
    in_coord.y = -in_coord.y; // spike here! (mirroring along y-axis)

    sign = (in_coord.y < 0.0) ? -1 : 1;

    in_coord.y = (in_coord.y < 0.0f) ? in_coord.y+= M_PI : in_coord.y;
    in_coord.y = (float) min(1.0f + in_coord.y/angle_step_rad, theta_max - 1);

    in_coord.x = (float) min(radius, (float)raster_size2);

    //sinc interpoaltion
    iul = (int)ceil(in_coord.x - L2);
    iul = (iul < 0) ? 0 : iul;

    iuh = (int)floor(in_coord.x + L2);
    iuh = (iuh > rho_max - 1) ? iuh = rho_max - 1 : iuh;

    ivl = (int)ceil(in_coord.y - L2);
    ivl = (ivl < 0) ? 0 : ivl;

    ivh = (int)floor(in_coord.y + L2);
    ivh = (ivh > theta_max - 1) ? ivh = theta_max - 1 : ivh;

    float kernel_y[20];
    for (i = ivl, j = 0; i <= ivh; ++i, ++j) {
      kernel_y[j] = tex1D(dfi_tex_ktbl, ktbl_len2 + (in_coord.y - (float)i) * table_spacing);
    }

    for (i = iul; i <= iuh; ++i) {
      kernel_x_val = tex1D(dfi_tex_ktbl, ktbl_len2 + (in_coord.x - (float)i) * table_spacing);

      for (k = ivl, j = 0; k <= ivh; ++k, ++j) {
        weight = kernel_y[j] * kernel_x_val;

	cufftComplex data_val = tex2D(dfi_tex_projections, (float)i, (float)k);

	res_real += data_val.x * weight;
	res_imag += data_val.y * weight;
      }
    }

    output[out_idx].x = res_real;
    output[out_idx].y = sign * res_imag;
  }
}

// __global__ static void  transfrom_complex_to_real(cufftComplex *input, float *output, int dim_x) {
//   int idx = blockIdx.x * BLOCK_SIZE + threadIdx.x;
//   int idy = blockIdx.y * BLOCK_SIZE + threadIdx.y;
//   long out_idx = idy * dim_x + idx;
//   output[out_idx] = input[out_idx].x;
// }

// __global__ static void  transfrom_real_to_complex(float *input, cufftComplex *output, int dim_x) {
//   int idx = blockIdx.x * BLOCK_SIZE + threadIdx.x;
//   int idy = blockIdx.y * BLOCK_SIZE + threadIdx.y;
//   long out_idx = idy * dim_x + idx;

//   output[out_idx].x = input[out_idx];
//   output[out_idx].y = 0.0f;
// }

__global__ static void  zeropadding_slice(float *input, cufftComplex *output, int dim_x, int padded_dim_x, int offset_x, int offset_y) {
  int idx = blockIdx.x * BLOCK_SIZE + threadIdx.x;
  int idy = blockIdx.y * BLOCK_SIZE + threadIdx.y;
  long in_idx = idy * dim_x + idx;
  long out_idx = (offset_y + idy) * padded_dim_x + (idx + offset_x);

  output[out_idx].x = input[in_idx];
  output[out_idx].y = 0.0f;
}

// __global__ static void  fftshift_complex(cufftComplex *input, cufftComplex *output, int rho_len) {
//   int idx = blockIdx.x * BLOCK_SIZE + threadIdx.x;
//   int idy = blockIdx.y * BLOCK_SIZE + threadIdx.y;
//   int rho_len2 = rho_len/2;
//   long in_idx = idy * rho_len + idx;
//   long out_idx = idy * rho_len + ((idx < rho_len2) ? idx + rho_len2 : idx - rho_len2);
//   output[out_idx] = input[in_idx];
// }

// __global__ static void  fftshift_real(float *input, float *output, int rho_len) {
//   int idx = blockIdx.x * BLOCK_SIZE + threadIdx.x;
//   int idy = blockIdx.y * BLOCK_SIZE + threadIdx.y;
//   int rho_len2 = rho_len/2;
//   long in_idx = idy * rho_len + idx;
//   long out_idx = idy * rho_len + ((idx < rho_len2) ? idx + rho_len2 : idx - rho_len2);
//   output[out_idx] = input[in_idx];
// }

__global__ static void  fftshift_c2r(cufftComplex *input, float *output, int rho_len) {
  int idx = blockIdx.x * BLOCK_SIZE + threadIdx.x;
  int idy = blockIdx.y * BLOCK_SIZE + threadIdx.y;
  int rho_len2 = rho_len/2;
  long in_idx = idy * rho_len + idx;
  long out_idx = idy * rho_len + ((idx < rho_len2) ? idx + rho_len2 : idx - rho_len2);

  output[out_idx] = input[in_idx].x;
}

__global__ static void  crop_region(float *input, float *output, int dim_x, int roi_dim_x, int offset_x, int offset_y) {
  int idx = blockIdx.x * BLOCK_SIZE + threadIdx.x;
  int idy = blockIdx.y * BLOCK_SIZE + threadIdx.y;
  long in_idx = (offset_y + idy) * dim_x + (idx + offset_x);
  long out_idx = (offset_y + idy) * roi_dim_x + idx;

  output[out_idx] = input[in_idx];
}

__global__ static void dfp_cuda_sinc_projector(float L2,
					       int slice_size_x,
					       int slice_size_y,
					       float angle_step_rad,
					       float table_spacing,
					       int theta_len,
					       int rho_len,
					       int rho_len2,
					       int ktbl_len2,
					       cufftComplex *slice,
					       cufftComplex *sino_output) {
  //variables
  float2 out_coord, slice_coord, norm_sino_coord;//, in_coord;
  int iul, iuh, ivl, ivh, i, j, k;
  float res_real, res_imag, weight, kernel_x_val, center;//, num_pixels;
  long out_idx;

  res_real = 0.0f, res_imag = 0.0f;
  out_idx = 0;
  center = rho_len2 - 1;

  //sino coords
  out_coord.x = (blockIdx.x * BLOCK_SIZE + threadIdx.x);
  out_coord.y = (blockIdx.y * BLOCK_SIZE + threadIdx.y);

  //sino polars
  norm_sino_coord.x = (out_coord.x < center) ? center - out_coord.x : out_coord.x - center;
  norm_sino_coord.y = (out_coord.x < center) ? M_PI + out_coord.y * angle_step_rad : out_coord.y * angle_step_rad;

  //slice cart coords (polar to cart)
  slice_coord.x = center - norm_sino_coord.x * cos(norm_sino_coord.y) + 0.5f;
  slice_coord.y = center + norm_sino_coord.x * sin(norm_sino_coord.y) + 0.5f;

  //sinc interpoaltion
  iul = (int)ceil(slice_coord.x - L2);
  iul = (iul < 0) ? 0 : iul;

  iuh = (int)floor(slice_coord.x + L2);
  iuh = (iuh > slice_size_x - 1) ? iuh = slice_size_x - 1 : iuh;

  ivl = (int)ceil(slice_coord.y - L2);
  ivl = (ivl < 0) ? 0 : ivl;

  ivh = (int)floor(slice_coord.y + L2);
  ivh = (ivh > slice_size_y - 1) ? ivh = slice_size_y - 1 : ivh;

  float kernel_y[20];
  for (i = ivl, j = 0; i <= ivh; ++i, ++j) {
    kernel_y[j] = tex1D(dfp_tex_ktbl, ktbl_len2 + (slice_coord.y - (float)i) * table_spacing);
  }

  for (i = iul; i <= iuh; ++i) {
    kernel_x_val = tex1D(dfp_tex_ktbl, ktbl_len2 + (slice_coord.x - (float)i) * table_spacing);

    for (k = ivl, j = 0; k <= ivh; ++k, ++j) {
      weight = kernel_y[j] * kernel_x_val;

      cufftComplex data_val = slice[k * slice_size_x + i];

      res_real += data_val.x * weight;
      res_imag += data_val.y * weight;
    }
  }

  if (out_coord.x <= center) {
    out_idx = out_coord.y * rho_len + norm_sino_coord.x;
  }
  else {
    out_idx = out_coord.y * rho_len + (rho_len - norm_sino_coord.x);
  }

  sino_output[out_idx].x = res_real / (float)(rho_len * rho_len);
  sino_output[out_idx].y = res_imag / (float)(rho_len * rho_len);
}

// __global__ static void dfp_cuda_sinc_projector_bi(float L2,
// 						  int slice_size_x,
// 						  int slice_size_y,
// 						  float angle_step_rad,
// 						  float table_spacing,
// 						  int theta_len,
// 						  int rho_len,
// 						  int rho_len2,
// 						  int ktbl_len2,
// 						  cufftComplex *slice,
// 						  cufftComplex *sino_output) {
//   //variables
//   float2 out_coord, slice_coord, norm_sino_coord; //, in_coord;
//   int /*iul, iuh, ivl, ivh, sign, i, j, k, */ x_coord00, x_coord01, y_coord00, y_coord10;
//   float  center; // weight, kernel_x_val, num_pixels;
//   long out_idx;

//   out_idx = 0;
//   center = rho_len2 - 1;

//   //sino coords
//   out_coord.x = (blockIdx.x * BLOCK_SIZE + threadIdx.x);
//   out_coord.y = (blockIdx.y * BLOCK_SIZE + threadIdx.y);

//   //sino polars
//   norm_sino_coord.x = (out_coord.x < center) ? center - out_coord.x : out_coord.x - center;
//   norm_sino_coord.y = (out_coord.x < center) ? M_PI + out_coord.y * angle_step_rad : out_coord.y * angle_step_rad;

//   //slice cart coords (polar to cart)
//   slice_coord.x = center - norm_sino_coord.x * cos(norm_sino_coord.y) + 0.5f;
//   slice_coord.y = center + norm_sino_coord.x * sin(norm_sino_coord.y) + 0.5f;

//   //(0,0)
//   x_coord00 = (int)floor(slice_coord.x);
//   x_coord00 = (x_coord00 < 0) ? 0 : x_coord00;
//   x_coord00 = (x_coord00 > slice_size_x - 1) ? slice_size_x - 1 : x_coord00;

//   y_coord00 = (int)floor(slice_coord.y);
//   y_coord00 = (y_coord00 < 0) ? 0 : y_coord00;
//   y_coord00 = (y_coord00 > slice_size_y - 1) ? slice_size_y - 1 : y_coord00;

//   //(0,1)
//   x_coord01 = (int)floor(slice_coord.x + 1);
//   x_coord01 = (x_coord01 < 0) ? 0 : x_coord01;
//   x_coord01 = (x_coord01 > slice_size_x - 1) ? slice_size_x - 1 : x_coord01;

//   //(1,0)
//   y_coord10 = (int)floor(slice_coord.y + 1);
//   y_coord10 = (y_coord10 < 0) ? 0 : y_coord10;
//   y_coord10 = (y_coord10 > slice_size_y - 1) ? slice_size_y - 1 : y_coord10;

//   cufftComplex val00 = slice[y_coord00 * slice_size_x + x_coord00];
//   cufftComplex val01 = slice[y_coord00 * slice_size_x + x_coord01];
//   cufftComplex val10 = slice[y_coord10 * slice_size_x + x_coord00];
//   cufftComplex val11 = slice[y_coord10 * slice_size_x + x_coord01];

//   float x_coef = ((float)slice_coord.x) - ((float)x_coord00);
//   float y_coef = ((float)slice_coord.y) - ((float)y_coord00);

//   float real_val_x = val00.x * (1.0f - x_coef) + val01.x * x_coef;
//   float imag_val_x = val00.y * (1.0f - x_coef) + val01.y * x_coef;

//   float real_val_x2 = val10.x * (1.0f - x_coef) + val11.x * x_coef;
//   float imag_val_x2 = val10.y * (1.0f - x_coef) + val11.y * x_coef;

//   float real_val = real_val_x * (1.0f - y_coef) +  real_val_x2 * y_coef;
//   float imag_val = imag_val_x * (1.0f - y_coef) +  imag_val_x2 * y_coef;

//   if (out_coord.x <= center) {
//     out_idx = out_coord.y * rho_len + norm_sino_coord.x;
//   }
//   else {
//     out_idx = out_coord.y * rho_len + (rho_len - norm_sino_coord.x);
//   }

//   sino_output[out_idx].x = real_val;
//   sino_output[out_idx].y = imag_val;
// }

__global__ void dfi_cuda_swap_quadrants_complex(cufftComplex *input, cufftComplex *output, int dim_x) {

  int idx = blockIdx.x * BLOCK_SIZE + threadIdx.x;
  int idy = blockIdx.y * BLOCK_SIZE + threadIdx.y;

  const int dim_y = gridDim.y * blockDim.y; //a half of real length

  output[idy * dim_x + idx] = input[(dim_y + idy) * dim_x + idx + 1];
  output[(dim_y + idy) * dim_x + idx] = input[idy * dim_x + idx + 1];
}

__global__ void dfi_cuda_swap_quadrants_real(cufftReal *output) {

  int idx = blockIdx.x * BLOCK_SIZE + threadIdx.x;
  int idy = blockIdx.y * BLOCK_SIZE + threadIdx.y;

  const int dim_x = gridDim.x * blockDim.x;
  int dim_x2 = dim_x/2, dim_y2 = dim_x2;
  long sw_idx1, sw_idx2;

  sw_idx1 = idy * dim_x + idx;

  cufftReal temp = output[sw_idx1];
  if (idx < dim_x2) {
    sw_idx2 = (dim_y2 + idy) * dim_x + (dim_x2 + idx);
    output[sw_idx1] = output[sw_idx2];
    output[sw_idx2] = temp;
  }
  else {
    sw_idx2 = (dim_y2 + idy) * dim_x + (idx - dim_x2);
    output[sw_idx1] = output[sw_idx2];
    output[sw_idx2] = temp;
  }
}

__global__ void swap_full_quadrants_complex(cufftComplex *output) {

  int idx = blockIdx.x * BLOCK_SIZE + threadIdx.x;
  int idy = blockIdx.y * BLOCK_SIZE + threadIdx.y;

  const int dim_x = gridDim.x * blockDim.x;
  int dim_x2 = dim_x/2, dim_y2 = dim_x2;
  long sw_idx1, sw_idx2;

  sw_idx1 = idy * dim_x + idx;

  cufftComplex temp = output[sw_idx1];
  if (idx < dim_x2) {
    sw_idx2 = (dim_y2 + idy) * dim_x + (dim_x2 + idx);
    output[sw_idx1] = output[sw_idx2];
    output[sw_idx2] = temp;
  }
  else {
    sw_idx2 = (dim_y2 + idy) * dim_x + (idx - dim_x2);
    output[sw_idx1] = output[sw_idx2];
    output[sw_idx2] = temp;
  }
}

__global__ void dfi_cuda_crop_roi(cufftReal *input, int x, int y, int roi_x, int roi_y, int raster_size, float scale, cufftReal *output) {

  int idx = blockIdx.x * BLOCK_SIZE + threadIdx.x;
  int idy = blockIdx.y * BLOCK_SIZE + threadIdx.y;

  if (idx < roi_x && idy < roi_y) {
    output[idy * roi_x + idx] = input[(idy + y) * raster_size + (idx + x)] * scale;
  }
}
 
int gpu_mainInit(Gpu_Context * self, float *filter) {
  int icount=0;
  
  if( self->gpuctx == NULL ) {
    
    cuInit(0);
    
    self->gpuctx = (void*)  malloc(sizeof(CUcontext)) ;
    // cudaSetDeviceFlags(    cudaDeviceMapHost   ) ;
    printf(" SETTING %d \n", self->MYGPU) ; 
    cudaSetDevice(self->MYGPU);
    
    cuCtxCreate( (CUcontext *) self->gpuctx   ,
		 // CU_CTX_SCHED_YIELD || CU_CTX_MAP_HOST ,
		 CU_CTX_SCHED_SPIN ,
		 self->MYGPU
		 ) ;
  }
  cuCtxSetCurrent ( *((CUcontext *) self->gpuctx  ))  ;
  
  //PP.add : allocate gpu memory and create plan for precondition ramp filter
  if (self->DO_PRECONDITION) {
    int num_bins = self->num_bins;
    
    {
      cudaError_t last = cudaGetLastError();
      if(last!=cudaSuccess) {
	printf("ERRORaaa: %s \n", cudaGetErrorString( last));
	exit(1);
      }
    }
    
    CUDA_SAFE_CALL(cudaMalloc(&self->precond_params_dl.d_r_sino_error, fftbunch*nextpow2_padded(num_bins)*sizeof(cufftReal)));
    CUDA_SAFE_CALL(cudaMalloc(&self->precond_params_dl.d_i_sino_error, fftbunch*nextpow2_padded(num_bins)*sizeof(cufftComplex)));
    
    
    CUDA_SAFE_FFT(cufftPlan1d((cufftHandle *) &self->precond_params_dl.planRamp_forward,(num_bins),CUFFT_R2C,fftbunch));
    CUDA_SAFE_FFT(cufftPlan1d((cufftHandle *) &self->precond_params_dl.planRamp_backward,(num_bins),CUFFT_C2R,fftbunch));
    
    cufftComplex* d_i_discrete_ramp = compute_discretized_ramp_filter((num_bins), self->precond_params_dl.d_r_sino_error, self->precond_params_dl.d_i_sino_error, self->precond_params_dl.planRamp_forward);
    self->precond_params_dl.filter_coeffs = d_i_discrete_ramp; //size : nextpow2_padded(num_bins)/2+1
    
  }
  
  if (self->USE_DFP) {
    //TODO : these as guru-user-parameters
    self->DFP_KERNEL_SIZE = 7;
    self->DFP_NOFVALUES = 2047;
    self->DFP_OVERSAMPLING_RATE = 2;
    
    puts("--------------------------------------------------------");
    puts("--------- Initializing Direct Fourier Projection -------");
    puts("--------------------------------------------------------");
    
    float angle_step = M_PI / (float)self->nprojs_span;
    
    //common params
    self->dfp_params.rho_len = self->num_bins;
    self->dfp_params.rho_len2 = self->num_bins/2;
    self->dfp_params.theta_len = self->nprojs_span;
    self->dfp_params.rho_ext_len = pow(2, ceil(log2f(self->num_bins))) * self->DFP_OVERSAMPLING_RATE;
    
    self->dfp_params.slice_size_x = self->num_bins;
    self->dfp_params.slice_size_y = self->num_bins;
    
    self->dfp_params.L = (float)self->DFP_KERNEL_SIZE;
    self->dfp_params.L2 = self->dfp_params.L/2.0f;
    
    self->dfp_params.ktbl_len = self->DFP_NOFVALUES;
    self->dfp_params.ktbl_len2 = (self->dfp_params.ktbl_len - 1)/2;

    self->dfp_params.table_spacing = (float)self->dfp_params.ktbl_len / self->dfp_params.L;
    self->dfp_params.angle_step_rad = angle_step;

    self->dfp_params.offset_x = self->dfp_params.rho_ext_len/2 - (int)self->dfp_params.slice_size_x/2;
    self->dfp_params.offset_y = self->dfp_params.rho_ext_len/2 - (int)self->dfp_params.slice_size_y/2;

    //grid sizes
    self->dfp_params.zeropad_grid_cols = dfi_cuda_calc_blocks(self->dfp_params.slice_size_x, BLOCK_SIZE, NULL);
    self->dfp_params.zeropad_grid_rows = dfi_cuda_calc_blocks(self->dfp_params.slice_size_y, BLOCK_SIZE, NULL);

    self->dfp_params.dfp_grid_cols = dfi_cuda_calc_blocks(self->dfp_params.rho_ext_len, BLOCK_SIZE, NULL);
    self->dfp_params.dfp_grid_rows = dfi_cuda_calc_blocks(self->dfp_params.theta_len, BLOCK_SIZE, NULL);

    self->dfp_params.swap_grid_cols = dfi_cuda_calc_blocks(self->dfp_params.rho_ext_len, BLOCK_SIZE, NULL);
    self->dfp_params.swap_grid_rows = dfi_cuda_calc_blocks(self->dfp_params.rho_ext_len/2, BLOCK_SIZE, NULL);

    self->dfp_params.shift_grid_cols = dfi_cuda_calc_blocks(self->dfp_params.rho_ext_len, BLOCK_SIZE, NULL);
    self->dfp_params.shift_grid_rows = dfi_cuda_calc_blocks(self->dfp_params.theta_len, BLOCK_SIZE, NULL);

    self->dfp_params.crop_grid_cols = dfi_cuda_calc_blocks(self->dfp_params.rho_len, BLOCK_SIZE, NULL);
    self->dfp_params.crop_grid_rows = dfi_cuda_calc_blocks(self->dfp_params.theta_len, BLOCK_SIZE, NULL);

    //memory allocations
    size_t slice_size = self->dfp_params.rho_ext_len * self->dfp_params.rho_ext_len;
    size_t sinogram_size = self->dfp_params.rho_ext_len * self->dfp_params.theta_len;
    size_t crop_sinogram_size = self->dfp_params.rho_len * self->dfp_params.theta_len;

    CUDA_SAFE_CALL(cudaMalloc((void**)&self->dfp_params.gpu_zeropadded_slice, slice_size * sizeof(cufftComplex)));
    CUDA_SAFE_CALL(cudaMemset(self->dfp_params.gpu_zeropadded_slice, 0, slice_size * sizeof(cufftComplex)));

    CUDA_SAFE_CALL(cudaMalloc((void**)&self->dfp_params.gpu_reconstructed_sinogram, sinogram_size * sizeof(cufftComplex)));
    CUDA_SAFE_CALL(cudaMemset(self->dfp_params.gpu_reconstructed_sinogram, 0, sinogram_size * sizeof(cufftComplex)));

    CUDA_SAFE_CALL(cudaMalloc((void**)&self->dfp_params.gpu_reconstructed_sinogram_real, sinogram_size * sizeof(float)));
    CUDA_SAFE_CALL(cudaMemset(self->dfp_params.gpu_reconstructed_sinogram_real, 0, sinogram_size * sizeof(float)));

    CUDA_SAFE_CALL(cudaMalloc((void**)&self->dfp_params.gpu_reconstructed_crop_sinogram_real, crop_sinogram_size * sizeof(float)));
    CUDA_SAFE_CALL(cudaMemset(self->dfp_params.gpu_reconstructed_crop_sinogram_real, 0, crop_sinogram_size * sizeof(float)));

    //init ktbl texture
    self->dfp_params.ktbl = dfi_cuda_get_ktbl(self->dfp_params.ktbl_len);
    dfp_tex_ktbl.normalized = false;
    dfp_tex_ktbl.filterMode = cudaFilterModeLinear;
    dfp_tex_ktbl.addressMode[0] = cudaAddressModeBorder;

    CUDA_SAFE_CALL(cudaMallocArray((cudaArray**)&(self->dfp_params.gpu_ktbl),
				   &(dfp_tex_ktbl.channelDesc),
				   self->dfp_params.ktbl_len));

    CUDA_SAFE_CALL(cudaMemcpyToArray((cudaArray*)self->dfp_params.gpu_ktbl,
				     0,
				     0,
				     self->dfp_params.ktbl,
				     (self->dfp_params.ktbl_len) * sizeof(float),
				     cudaMemcpyHostToDevice));

    CUDA_SAFE_CALL(cudaBindTextureToArray(dfp_tex_ktbl, (cudaArray*)self->dfp_params.gpu_ktbl));

    //setup fft plans
    CUDA_SAFE_FFT(cufftPlan1d(&self->dfp_params.ifft1d_plan, self->dfp_params.rho_ext_len, CUFFT_C2C, self->dfp_params.theta_len));
    CUDA_SAFE_FFT(cufftPlan2d(&self->dfp_params.fft2d_plan, self->dfp_params.rho_ext_len, self->dfp_params.rho_ext_len, CUFFT_C2C));
  }



  if ( self->USE_DFI || self->FBFILTER == 10) {

    puts("--------------------------------------------------------");
    puts("--------- Initializing Direct Fourier Inversion --------");
    puts("--------------------------------------------------------");

    // self->DFI_OVERSAMPLING_RATE = 2;
    // self->DFI_KERNEL_SIZE = 9;
    // self->DFI_NOFVALUES = 2047;
    // self->DFI_R2C_MODE = 0;

    float angle_step = M_PI / (float)self->nprojs_span;

    int cutted_rho_len = 0;

    if (self->axis_position >= self->num_bins/2) {
      cutted_rho_len = (self->num_bins - self->axis_position) * 2;
    }
    else {
      cutted_rho_len = self->axis_position * 2;
    }

    self->dfi_params.oversampling = self->DFI_OVERSAMPLING_RATE;
    self->dfi_params.rho_len = cutted_rho_len;
    self->dfi_params.rho_ext_len = pow(2, ceil(log2f(self->dfi_params.rho_len))) * self->dfi_params.oversampling;
    self->dfi_params.nprojs_span = self->nprojs_span;
    self->dfi_params.L = (float)self->DFI_KERNEL_SIZE;
    self->dfi_params.L2 = self->dfi_params.L/2.0f;
    self->dfi_params.ktbl_len = self->DFI_NOFVALUES;

    if (!(self->dfi_params.ktbl_len%2)) {
      printf("Length of presampled kernel cannot be even.");
      --self->dfi_params.ktbl_len;
    }

    self->dfi_params.ktbl_len2 = (self->dfi_params.ktbl_len - 1)/2;
    self->dfi_params.raster_size = self->dfi_params.rho_ext_len;
    self->dfi_params.raster_size2 = self->dfi_params.raster_size/2;
    self->dfi_params.table_spacing = (float)self->dfi_params.ktbl_len / self->dfi_params.L;
    self->dfi_params.angle_step_rad = angle_step;
    self->dfi_params.theta_max = self->nprojs_span;
    self->dfi_params.rho_max = self->dfi_params.rho_ext_len/2;
    self->dfi_params.roi_x = self->num_x;
    self->dfi_params.roi_y = self->num_y;
    self->dfi_params.roi_start_x = ((self->dfi_params.raster_size - self->dfi_params.roi_x)/2);
    self->dfi_params.roi_start_y = ((self->dfi_params.raster_size - self->dfi_params.roi_y)/2);
    self->dfi_params.scale = 1.0/(float)(self->dfi_params.raster_size * self->dfi_params.raster_size);

    if (self->DFI_R2C_MODE) {
      self->dfi_params.fft_sino_dim = (dfi_cuda_calc_blocks((self->dfi_params.rho_ext_len/2 + 1) * 2, BLOCK_SIZE, NULL) + 1) * BLOCK_SIZE;
    }

    float spectrum_scaling_rate = (float)self->dfi_params.rho_len/(float)self->dfi_params.rho_ext_len;

    self->dfi_params.pps_grid_cols = dfi_cuda_calc_blocks(self->dfi_params.rho_ext_len, BLOCK_SIZE, NULL);
    self->dfi_params.pps_grid_rows = dfi_cuda_calc_blocks(self->dfi_params.nprojs_span, BLOCK_SIZE, NULL);
    self->dfi_params.interp_grid_cols = dfi_cuda_calc_blocks((self->dfi_params.rho_ext_len/2 + 1) * spectrum_scaling_rate, BLOCK_SIZE, NULL);
    self->dfi_params.interp_grid_rows = dfi_cuda_calc_blocks(self->dfi_params.rho_ext_len * spectrum_scaling_rate, BLOCK_SIZE, NULL);
    self->dfi_params.swap_grid_cols = dfi_cuda_calc_blocks(self->dfi_params.raster_size/2, BLOCK_SIZE, NULL);
    self->dfi_params.swap_grid_rows = dfi_cuda_calc_blocks(self->dfi_params.raster_size/2, BLOCK_SIZE, NULL);
    self->dfi_params.swap_quad_grid_cols = dfi_cuda_calc_blocks(self->dfi_params.raster_size, BLOCK_SIZE, NULL);
    self->dfi_params.swap_quad_grid_rows = dfi_cuda_calc_blocks(self->dfi_params.raster_size/2, BLOCK_SIZE, NULL);
    self->dfi_params.roi_grid_cols = dfi_cuda_calc_blocks(self->num_x, BLOCK_SIZE, NULL);
    self->dfi_params.roi_grid_rows = dfi_cuda_calc_blocks(self->num_y, BLOCK_SIZE, NULL);

    self->dfi_params.spectrum_offset_y = (self->dfi_params.raster_size - self->dfi_params.interp_grid_rows * BLOCK_SIZE)/2;
    self->dfi_params.max_radius = (self->dfi_params.interp_grid_rows * BLOCK_SIZE)/2.0;

    //memory allocations
    CUDA_SAFE_CALL(cudaMalloc((void**)&self->dfi_params.gpu_truncated_sino,
			      self->dfi_params.nprojs_span * self->dfi_params.rho_len * sizeof(float)));
    CUDA_SAFE_CALL(cudaMemset(self->dfi_params.gpu_truncated_sino, 0,
			      self->dfi_params.nprojs_span * self->dfi_params.rho_len * sizeof(float)));

    if (self->DFI_R2C_MODE) {
      CUDA_SAFE_CALL(cudaMalloc((void**)&self->dfi_params.gpu_zeropadded_sino,
				self->dfi_params.nprojs_span * self->dfi_params.fft_sino_dim * sizeof(float)));
      CUDA_SAFE_CALL(cudaMemset(self->dfi_params.gpu_zeropadded_sino, 0,
				self->dfi_params.nprojs_span * self->dfi_params.fft_sino_dim * sizeof(float)));
    }
    else {
      CUDA_SAFE_CALL(cudaMalloc((void**)&self->dfi_params.gpu_input,
				self->dfi_params.nprojs_span * self->dfi_params.rho_ext_len * sizeof(cufftComplex)));
      CUDA_SAFE_CALL(cudaMemset(self->dfi_params.gpu_input, 0,
				self->dfi_params.nprojs_span * self->dfi_params.rho_ext_len * sizeof(cufftComplex)));
    }

    CUDA_SAFE_CALL(cudaMalloc((void**)&self->dfi_params.gpu_spectrum,
			      (self->dfi_params.raster_size/2 + 1) * self->dfi_params.raster_size * sizeof(cufftComplex)));
    CUDA_SAFE_CALL(cudaMemset(self->dfi_params.gpu_spectrum, 0,
			      (self->dfi_params.raster_size/2 + 1) * self->dfi_params.raster_size * sizeof(cufftComplex)));

    CUDA_SAFE_CALL(cudaMalloc((void**)&self->dfi_params.gpu_swapped_spectrum,
			      (self->dfi_params.raster_size/2 + 1) * self->dfi_params.raster_size * sizeof(cufftComplex)));
    CUDA_SAFE_CALL(cudaMemset(self->dfi_params.gpu_swapped_spectrum, 0,
			      (self->dfi_params.raster_size/2 + 1) * self->dfi_params.raster_size * sizeof(cufftComplex)));

    CUDA_SAFE_CALL(cudaMalloc((void**)&self->dfi_params.gpu_c2r_result,
			      self->dfi_params.raster_size * self->dfi_params.raster_size * sizeof(float)));
    CUDA_SAFE_CALL(cudaMemset(self->dfi_params.gpu_c2r_result, 0,
			      self->dfi_params.raster_size * self->dfi_params.raster_size * sizeof(float)));

    CUDA_SAFE_CALL(cudaMalloc((void**)&self->dfi_params.gpu_output,
			      self->dfi_params.roi_x * self->dfi_params.roi_y * sizeof(float)));
    CUDA_SAFE_CALL(cudaMemset(self->dfi_params.gpu_output, 0,
			      self->dfi_params.roi_x * self->dfi_params.roi_y * sizeof(float)));

    //init projection texture
    dfi_tex_projections.normalized = false;
    dfi_tex_projections.filterMode = cudaFilterModePoint;
    dfi_tex_projections.addressMode[0] = cudaAddressModeMirror;
    dfi_tex_projections.addressMode[1] = cudaAddressModeWrap;

    //init ktbl texture
    self->dfi_params.ktbl = dfi_cuda_get_ktbl(self->dfi_params.ktbl_len);

    dfi_tex_ktbl.normalized = false;
    dfi_tex_ktbl.filterMode = cudaFilterModeLinear;
    dfi_tex_ktbl.addressMode[0] = cudaAddressModeBorder;

    //create plans
    if (self->DFI_R2C_MODE) {
      const int nrank = 1;
      int n[nrank] = {self->dfi_params.rho_ext_len};
      int inembed[nrank] = {self->dfi_params.rho_ext_len};
      int istride = 1;
      int idist = self->dfi_params.fft_sino_dim;
      int onembed[nrank] = {self->dfi_params.rho_ext_len/2};
      int ostride = 1;
      int odist = self->dfi_params.fft_sino_dim/2;

      CUDA_SAFE_FFT(cufftPlanMany(&self->dfi_params.fft1d_plan, nrank, n,
				  inembed, istride, idist,
				  onembed, ostride, odist,
				  CUFFT_R2C, self->dfi_params.nprojs_span));
    }
    else {
      CUDA_SAFE_FFT(cufftPlan1d(&self->dfi_params.fft1d_plan, self->dfi_params.rho_ext_len, CUFFT_C2C, self->dfi_params.nprojs_span));
    }

    CUDA_SAFE_FFT(cufftPlan2d(&self->dfi_params.ifft2d_plan, self->dfi_params.rho_ext_len, self->dfi_params.rho_ext_len, CUFFT_C2R));

    // cufftSetCompatibilityMode(self->dfi_params.ifft2d_plan, CUFFT_COMPATIBILITY_NATIVE);
    // cufftSetCompatibilityMode(self->dfi_params.fft1d_plan, CUFFT_COMPATIBILITY_NATIVE);
  } //end of DFI initialization





  



 // creare qui gli stream usando i type cast come al solito da void

  if(filter[0] > 1.0e6) { // this should never happen in normal cases

    filter[0]-=2.0e6; // remove the trick-out added term
    CUDA_SAFE_CALL(cudaMemcpy(self->dev_Filter,filter,sizeof(float)*(self->dim_fft),cudaMemcpyHostToDevice));

    return 1;
  }



  // cudaSetDeviceFlags(    cudaDeviceMapHost   ) ;
  //cuCtxGetCurrent ( (CUcontext *) self->gpuctx  ) ;
  CUDA_SAFE_CALL(cudaMalloc((void **)&(self->dev_rWork),  TWO2by2*sizeof(float)*self->dim_fft*iAlignUp(self->nprojs_span*self->VECTORIALITY,fftbunch)));

  CUDA_SAFE_CALL(cudaMalloc((void**)&(self->dev_iWork)  ,    TWO2by2*sizeof(cufftComplex)*(self->dim_fft/2 +1)*fftbunch));
  CUDA_SAFE_CALL(cudaMalloc((void**)&(self->dev_iWork_copy)    ,sizeof(cufftComplex)*(self->dim_fft/2 +1)*fftbunch));

  if (self->SF_ITERATIONS) { // angle-dependent filtering
    if (self->verbosity > 1) puts("Using SIRT-Filter");
    CUDA_SAFE_CALL(cudaMalloc((void**) &(self->dev_Filter), self->dim_fft * self->nprojs_span * sizeof(float)));
    CUDA_SAFE_CALL(cudaMemcpy(self->dev_Filter, filter, self->dim_fft * self->nprojs_span * sizeof(float), cudaMemcpyHostToDevice));
  }
  else {
    CUDA_SAFE_CALL(cudaMalloc((void**)&(self->dev_Filter)   ,sizeof(float)*(self->dim_fft))                     );
    CUDA_SAFE_CALL(cudaMemcpy(self->dev_Filter,filter,sizeof(float)*(self->dim_fft),cudaMemcpyHostToDevice));
  }
  //   CUDA_SAFE_FFT(cufftPlan1d( (cufftHandle *) &(self->planR2C),self->dim_fft,CUFFT_R2C,self->nprojs_span));
  //   CUDA_SAFE_FFT(cufftPlan1d( (cufftHandle *) &(self->planC2R),self->dim_fft,CUFFT_C2R,self->nprojs_span));
  CUDA_SAFE_FFT(cufftPlan1d( (cufftHandle *) &(self->planR2C),self->dim_fft,CUFFT_R2C,fftbunch));
  CUDA_SAFE_FFT(cufftPlan1d( (cufftHandle *) &(self->planC2R),self->dim_fft,CUFFT_C2R,fftbunch));
  
  CUDA_SAFE_FFT(cufftPlan1d( (cufftHandle *) &(self->planC2C_2by2),self->dim_fft,CUFFT_C2C,fftbunch));


  if(self->verbosity>0) printf("gpu_mainInit icount %d \n" , icount);
  icount++ ;

  // ------------------------------------------------------------------------------------------------------
  // cudaChannelFormatDesc floatTex = cudaCreateChannelDesc<float>();
  CUDA_SAFE_CALL( cudaMallocArray((cudaArray**) &(self->a_Proje_voidptr), &floatTex ,  self->num_bins    , self->nprojs_span ) );


  CUDA_SAFE_CALL( cudaMallocArray((cudaArray**) &(self->a_cProje_voidptr), &texcProjes.channelDesc ,  self->num_bins    , self->nprojs_span ) );

  
  CUDA_SAFE_CALL(cudaMalloc((void **)&(self->dev_Work_perproje)   ,TWO2by2*sizeof(float)*self->num_bins*self->nprojs_span));


  //   CUDA_SAFE_CALL(cudaMalloc((void**)&(self->d_axis_s)   ,sizeof(float)*(self->nprojs_span)) );
  //   CUDA_SAFE_CALL( cudaMemcpy(self->d_axis_s , self->axis_position_s   , sizeof(float) *self->nprojs_span   ,  cudaMemcpyHostToDevice) );

  // printf("gpu_mainInit icount %d \n" , icount); icount++ ;

  // printf("HELLO ZUBAIR. ( from maininit) the calculation is broken in 32x32 tiles \n");

  self->NblocchiPerLinea32   =(int ) (  self->num_x *1.0/32  +0.9999999999 );
  self->NblocchiPerColonna32 =(int ) (  self->num_y *1.0/32  +0.9999999999 )  ;

  self->NblocchiPerLinea   =  2* self->NblocchiPerLinea32     ;
  self->NblocchiPerColonna =  2* self->NblocchiPerColonna32   ;


  // crea il volume_d multiplo

  self->dimrecx = self->NblocchiPerLinea32*32 ;
  self->dimrecy = self->NblocchiPerColonna32*32 ;


  CUDA_SAFE_CALL(cudaMalloc((void**)&(self->d_SLICE) , TWO2by2*sizeof(float) * self->dimrecx* self->dimrecy      ));

  CUDA_SAFE_CALL(cudaMemset(self->d_SLICE,0, TWO2by2*sizeof(float) * self->dimrecx* self->dimrecy    ));


  CUDA_SAFE_CALL(cudaMalloc((void**)&(self->d_cos_s  ), sizeof(float) *   self->numpjs    ));

  CUDA_SAFE_CALL(cudaMalloc((void**)&(self->d_sin_s  ), sizeof(float) *   self->numpjs    ));

  CUDA_SAFE_CALL(cudaMalloc((void**)&(self->d_axis_s ), sizeof(float) *   self->numpjs    ));

  if(self->verbosity>0)printf("gpu_mainInit icount %d \n" , icount);
  icount++ ;



  CUDA_SAFE_CALL( cudaMemcpy(self->  d_cos_s , self->cos_s              , sizeof(float) *  self->numpjs ,  cudaMemcpyHostToDevice) );
  CUDA_SAFE_CALL( cudaMemcpy(self->  d_sin_s,  self->sin_s              , sizeof(float) *  self->numpjs ,  cudaMemcpyHostToDevice) );
  CUDA_SAFE_CALL( cudaMemcpy(self-> d_axis_s , self->axis_position_s    , sizeof(float) *  self->numpjs ,  cudaMemcpyHostToDevice) );


  self->gpu_streams = new  Gpu_Streams ;
  cudaStreamCreate(&((((Gpu_Streams*) self->gpu_streams)->stream[0])) );
  cudaStreamCreate(&((((Gpu_Streams*) self->gpu_streams)->stream[1])) );

  {
    cudaError_t last = cudaGetLastError();
    if(last!=cudaSuccess) {
      printf("ERROR0: %s \n", cudaGetErrorString( last));
      exit(1);
    }
  }


  if(self->lt_infos_coarse) {
    int n =  self->lt_infos_coarse->SLD/self->lt_infos_coarse->nprojs ;
    int batch =  self->lt_infos_coarse->nprojs * self->lt_infos_coarse->Nsigmas ; 
    
    CUDA_SAFE_FFT( cufftPlanMany( (cufftHandle *) &(self->lt_planr2c), 1, (int *) &( n ) ,
				  NULL , 0, 0, NULL , 0, 0,
				  CUFFT_R2C,  batch); );

    CUDA_SAFE_FFT( cufftPlanMany( (cufftHandle *) &(self->lt_planc2r), 1, (int *) &( n ) ,
				  NULL , 0, 0, NULL , 0, 0,
				  CUFFT_C2R ,  batch); );


    
    
    printf("LT cuda plans creati \n");
  }

  return 1;
}


extern "C" {
  int gpu_fbdl(Gpu_Context * self, float *d_S , float * SLICE, int do_precondition,
	       float DETECTOR_DUTY_RATIO,
	       int DETECTOR_DUTY_OVERSAMPLING,
	       int doppio, int memisonhost );
}
extern "C" {
  //int dfi_gpu_main(Gpu_Context * self, float *WORK , float * SLICE, int memisonhost);
}



int gpu_fbdl(Gpu_Context * self, float *d_S , float * SLICE, int do_precondition,
	     float DETECTOR_DUTY_RATIO,
	     int DETECTOR_DUTY_OVERSAMPLING,
	     int doppio, int memisonhost=1 ) {
  assert(do_precondition==0);

  cuCtxSetCurrent ( *((CUcontext *) self->gpuctx  ))  ;
  {
    cudaError_t last = cudaGetLastError();
    if(last!=cudaSuccess) {
      printf("ERROR1: %s \n", cudaGetErrorString( last));
      exit(1);
    }
  }

  for(int iv=0; iv<doppio; iv++)    {
    {
      dim3 blk,grd;
      blk = dim3( blsize_cufft , 1 , 1 );
      grd = dim3( iDivUp(self->num_bins,blsize_cufft) , 1, 1 );
      if(   self->fai360==0) {
	kern_recopy<<<grd,blk>>>(self->num_bins,self->dim_fft*0+self->num_bins,self->nprojs_span,
				 self->dev_Work_perproje   ,
				 d_S + iv * self->num_bins*self->nprojs_span
				 );
      } else {
	kern_recopy_slope_leastsquare<<<grd,blk>>>(self->num_bins,self->dim_fft*0+self->num_bins,self->nprojs_span,
						   self->dev_Work_perproje ,
						   d_S+ iv * self->num_bins*self->nprojs_span,
						   self->overlapping, self->pente_zone, self->flat_zone,
						   self->d_axis_s,
						   self->prof_shift  , self->prof_fact
						   );
      }
    }
    {
      cudaError_t last = cudaGetLastError();
      if(last!=cudaSuccess) {
	printf("ERROR: %s \n", cudaGetErrorString( last));
	exit(1);
      }
    }

    CUDA_SAFE_CALL( cudaMemcpyToArray((cudaArray*) self->a_Proje_voidptr, 0, 0,self->dev_Work_perproje ,
				      self->nprojs_span*self->num_bins*sizeof(float) , cudaMemcpyDeviceToDevice) );
    CUDA_SAFE_CALL( cudaBindTextureToArray(texProjes,(cudaArray*) self->a_Proje_voidptr) );  //  !! unbind ??
    texProjes.filterMode = cudaFilterModeLinear;

    // da fare : testare prima la copia di memoria in blocco 2D ===> OK
    // Allocare con pinned da CCspace.cx
    // async cambiare al volo self->NblocchiPerColonna e incrementare la posizione in y. si usera offset nel kernel e si
    // sciftera il float * self->d_SLICE
    // ASINC ANCORA NON FUNZIONA, PROBLEMI PROBABILMENTE CON LA MEMORIA PINNED
    {
      dim3      dimBlock         (  16  ,  16   );
      int itime=0;
      {
	dim3 dimGrid ( self->NblocchiPerLinea32 ,   self->NblocchiPerColonna32  );
	// dim3 dimGrid ( self->NblocchiPerLinea ,   self->NblocchiPerColonna  );
	if(self->CONICITY_FAN ==0) {
	  gputomo_kernel32<<<dimGrid,dimBlock,256*3*sizeof(float),(((Gpu_Streams*) self->gpu_streams)->stream[itime%2])>>>
	    (self->nprojs_span,self->num_bins, self->axis_position,
	     self->d_SLICE,
	     self->gpu_offset_x, self->gpu_offset_y ,
	     self->d_cos_s , self->d_sin_s , self->d_axis_s
	     );
	} else {

	  gputomo_kernel32_fan<<<dimGrid,dimBlock,256*3*sizeof(float),(((Gpu_Streams*) self->gpu_streams)->stream[itime%2])>>>
	    (self->nprojs_span,self->num_bins, self->axis_position,
	     self->d_SLICE,
	     self->gpu_offset_x, self->gpu_offset_y ,
	     self->d_cos_s , self->d_sin_s , self->d_axis_s, self->FAN_FACTOR,
	     self->SOURCE_X
	     );
	}
      }

      if(abs(DETECTOR_DUTY_OVERSAMPLING)>1){
	assert( self->num_x==self->num_y  );
	int dimslice = self->num_x ;

	int NblocchiPerLinea   =  self->NblocchiPerLinea    ;
	// int NblocchiPerColonna =  self->NblocchiPerColonna  ;

	int dimrecx; // , dimrecy;

	dimrecx = NblocchiPerLinea  *16 ;
	// dimrecy = NblocchiPerColonna*16 ;

	float dangle = acos(self->cos_s[1]*self->cos_s[0]+self->sin_s[1]*self->sin_s[0])*DETECTOR_DUTY_RATIO;

	dim3 dimGrid ( NblocchiPerLinea ,   NblocchiPerLinea );
	dim3      dimBlock         (  16  ,  16   );

	cudaArray * a_slice;
	// cudaChannelFormatDesc floatTex = cudaCreateChannelDesc<float>();

	CUDA_SAFE_CALL( cudaMallocArray(&a_slice, &floatTex ,  (dimslice+2)   ,  (dimslice+2)) );


	{
	  float zeri[dimslice+2];
	  memset(zeri,0, (dimslice+2)*sizeof(float));
	  CUDA_SAFE_CALL( cudaMemcpy2DToArray(a_slice, 0, 0, zeri , (dimslice+2)*sizeof(float) ,(dimslice+2)*sizeof(float) , 1,cudaMemcpyHostToDevice) );
	  CUDA_SAFE_CALL( cudaMemcpy2DToArray(a_slice, 0, 0, zeri , (1)*sizeof(float) ,(1)*sizeof(float) ,(dimslice+2) ,cudaMemcpyHostToDevice) );
	  CUDA_SAFE_CALL( cudaMemcpy2DToArray(a_slice, (dimslice+1)*4, 0, zeri , (1)*sizeof(float) ,
					      (1)*sizeof(float) ,(dimslice+2) ,cudaMemcpyHostToDevice) );
	  CUDA_SAFE_CALL( cudaMemcpy2DToArray(a_slice, 0, dimslice+1, zeri , (dimslice+2)*sizeof(float) ,(dimslice+2)*sizeof(float) , 1,cudaMemcpyHostToDevice) );
	}

	CUDA_SAFE_CALL( cudaMemcpy2DToArray(a_slice, 1*4, 1, self->d_SLICE , (dimrecx)*sizeof(float) ,(dimslice)*sizeof(float) , dimslice,
					    cudaMemcpyDeviceToDevice) );


	texProjes.filterMode = cudaFilterModeLinear;
	texProjes.addressMode[0] = cudaAddressModeClamp;
	texProjes.addressMode[1] = cudaAddressModeClamp;
	texProjes.filterMode = cudaFilterModeLinear;
	texProjes.normalized = false;
	CUDA_SAFE_CALL( cudaBindTextureToArray(texProjes,(cudaArray*) a_slice) );  //  !! unbind ??


	// printf("gputomo.cu:  DOING slicerot_kernel with DETECTOR_DUTY_OVERSAMPLING=%d\n",DETECTOR_DUTY_OVERSAMPLING);
	slicerot_kernel<<<dimGrid,dimBlock>>>( dimslice,
					       self->d_SLICE,
					       dangle,
					       abs(DETECTOR_DUTY_OVERSAMPLING) );
	CUDA_SAFE_CALL(cudaFreeArray(a_slice));
      }
      if(memisonhost) {
	cudaMemcpy2D(
		     SLICE  +iv* self->num_x*self->num_y   ,self->num_x*sizeof(float),
		     self->d_SLICE ,
		     self->dimrecx*sizeof(float),
		     self->num_x*sizeof(float),
		     self->num_y,
		     cudaMemcpyDeviceToHost
		     );
      } else {
	cudaMemcpy2D(
		     SLICE  +iv* self->num_x*self->num_y   ,self->num_x*sizeof(float),
		     self->d_SLICE ,
		     self->dimrecx*sizeof(float),
		     self->num_x*sizeof(float),
		     self->num_y,
		     cudaMemcpyDeviceToDevice
		     );
      }

      cudaError_t last = cudaGetLastError();
      if(last!=cudaSuccess) {
	printf("memcpy async Last error-reset: %s \n", cudaGetErrorString( last));
	exit(1);
      }
    }
  }
  return 1;
}

__global__  static  void kern_shrink_fista(int nitems,int ncomps, float *d_w,  float *d_dw, float *d_wold,  float  weight,
					   float *d_notzero, float told,  float tnew  ){
  int gid;
  float w0,w1,tmp,  notzero, wold;
  gid = threadIdx.x + blockIdx.x*blockDim.x;
  // int iwidget = gid/ncomps  ;

  if(gid<nitems*ncomps) {
    // ss = d_ss[iwidget];
    w0 = d_w[gid];
    tmp = w0+d_dw[gid];
    // w1 = copysignf( max(fabs(tmp)-weight*ss,0.0f) , tmp) ;
    w1 = copysignf( max(fabs(tmp)-weight,0.0f) , tmp) ;
    wold = d_wold[gid];
    d_wold[gid]=w1 ;
    w1=w1 +  ((told-1)/tnew) * (w1-wold) ;
    d_w [gid] = w1;
    notzero=0.0f;
    if(w1!=0.0f) {
      notzero = 1.0f;
    }
    d_dw[gid] = w1 - w0;
    if(d_notzero)   d_notzero[gid]=notzero;
  }
}

// Commented and replaced with JL piece of code.

__global__  static  void   kern_subtract(float *d_R, float *d_vect , float *d_w , int npoints, int  sizepatch  ) {
  int gid;
  gid = threadIdx.x + blockIdx.x*blockDim.x;
  int np;
  np = gid/sizepatch;
  if(gid <npoints ) {
    d_R[gid]-= d_vect[gid]*d_w[np];
  }
}



// __global__  static  void   kern_diff_pen_l1_debug(int todo , int done , float *d_w ,float * d_p , float * d_diffpen , float alpha ,  float weight ) {
//   int gid,isign=0;
//   float t;
//   gid = threadIdx.x + blockIdx.x*blockDim.x+done;
//   if(gid < todo+done) {
//     t = d_w[gid]+alpha*d_p[gid];
//     if(t>0.0f) {
//       isign=+1 ;
//       d_diffpen[gid] = - isign*weight;
//     }
//     //else {
//     if (t<0.0f) {
//       isign=-1 ;
//       d_diffpen[gid] = - isign*weight;
//     }
//     if (t==0.0f) {
//       d_diffpen[gid] = 0.0f;
//     }
//     // }
//   }
// }



__global__  static  void   kern_complete_grad_l1(int todo , int done , float *d_w ,float * d_df , float * d_dw ,  float weight ) {
  int gid;
  float isign=0.0f;

  gid = threadIdx.x + blockIdx.x*blockDim.x+done;

  if(gid < todo+done) {

    if(d_w[gid]>0.0f) {
      isign=+1.0f ;
    }
    //else {
    if (d_w[gid]<0.0f) {
      isign=-1.0f ;
    }

    if (d_w[gid]==0.0f) {
      // if(fabs(d_dw[gid])>weight) {
      if( d_dw[gid] != 0.0f ) {
	isign=   copysignf( 1.0f, d_dw[gid] ) ;
      }
      isign=0.0f;//DEBUG - matrice folle
    }
    d_df[gid] = d_dw[gid]- isign*weight;

    // }
  }
}

__global__  static  void    kern_dp_and_velocita( int n,
						  float * d_dfprojected, float * d_df, float *d_dw,
						  float * d_p , float beta  ,
						  float * forreduce,float * forreduceq ) {

  int gid;
  double toreduce=0.0;
  double toreduceq=0.0;
  int tid = threadIdx.x ;
  int gridsize = gridDim.x*blockDim.x; // int gridsize = WKSIZE*blockDim.x;

  volatile __shared__ double chache[WKSIZE] ;
  volatile __shared__ double chache_q[WKSIZE] ;

  gid = threadIdx.x + blockIdx.x*blockDim.x;

  while(gid<n) {
    d_p[gid] = beta * d_p[gid]+d_dfprojected[gid];
    toreduce  +=   d_p[gid]  * d_df[gid]    ;
    toreduceq +=   d_p[gid]  * d_dw[gid]    ;
    gid += gridsize ;
  }


  chache[ threadIdx.x  ] = toreduce  ;
  chache_q[ threadIdx.x  ] = toreduceq  ;
  __syncthreads();

  if(WKSIZE>=512) {  if(tid<256) { chache[tid] += chache [tid + 256]; chache_q[tid] += chache_q [tid + 256]; } __syncthreads();  }
  if(WKSIZE>=256) {  if(tid<128) { chache[tid] += chache [tid + 128]; chache_q[tid] += chache_q [tid + 128]; } __syncthreads();  }
  if(WKSIZE>=128) {  if(tid<64)  { chache[tid]  += chache [tid + 64]; chache_q[tid] += chache_q [tid + 64]; } __syncthreads();  }
  if(tid<32) {
    if(WKSIZE>=64) { chache[tid]  += chache [tid + 32]; chache_q[tid]  += chache_q [tid + 32];}
    if(WKSIZE>=32) { chache[tid]  += chache [tid + 16]; chache_q[tid]  += chache_q [tid + 16];}
    if(WKSIZE>=16) { chache[tid]  += chache [tid + 8];  chache_q[tid]  += chache_q [tid + 8]; }
    if(WKSIZE>=8)  { chache[tid]  += chache [tid + 4];  chache_q[tid]  += chache_q [tid + 4]; }
    if(WKSIZE>=4)  { chache[tid]  += chache [tid + 2];  chache_q[tid]  += chache_q [tid + 2]; }
    if(WKSIZE>=2)  { chache[tid]  += chache [tid + 1];  chache_q[tid]  += chache_q [tid + 1]; }
  }
  if ( threadIdx.x == 0 ) {
    forreduce[blockIdx.x] = chache [0] ;
    forreduceq[blockIdx.x] = chache_q [0] ;
  }
}
__global__  static  void   kern_diff_pen_l1(int n , float *d_w ,float * d_p  , float alpha ,  float weight, float * forreduce ) {
  volatile  __shared__ double chache[WKSIZE] ;
  int gid;
  double toreduce=0.0;
  double isign=0.0;
  float t;//,t1,t2,t3;
  int tid = threadIdx.x ;
  int gridsize = gridDim.x*blockDim.x; // int gridsize = WKSIZE*blockDim.x;
  gid = tid+ blockIdx.x*blockDim.x;

  while(gid<n) {
    t = d_w[gid]+alpha*d_p[gid];
    if(t>0.0f) {
      isign = -weight;
    }
    if (t<0.0f) {
      isign =  weight;
    }
    toreduce += isign *d_p[gid] ;
    // toreduce += -  ( (t==0.0f) )*weight *d_p[gid] ;
    gid+=gridsize;
  }

  chache[ threadIdx.x  ] = toreduce  ;
  __syncthreads () ;

  if(WKSIZE>=512) {  if(tid<256) { chache[tid] += chache [tid + 256]; } __syncthreads();  }
  if(WKSIZE>=256) {  if(tid<128) { chache[tid] += chache [tid + 128]; } __syncthreads();  }
  if(WKSIZE>=128) {  if(tid<64) { chache[tid]  += chache [tid + 64]; } __syncthreads();  }
  if(tid<32) {
    if(WKSIZE>=64) chache[tid]  += chache [tid + 32];
    if(WKSIZE>=32) chache[tid]  += chache [tid + 16];
    if(WKSIZE>=16) chache[tid]  += chache [tid + 8];
    if(WKSIZE>=8) chache[tid]  += chache [tid + 4];
    if(WKSIZE>=4) chache[tid]  += chache [tid + 2];
    if(WKSIZE>=2) chache[tid]  += chache [tid + 1];
  }
  if ( tid == 0 ) {
    forreduce[blockIdx.x] = chache [0] ;
  }
}
__global__  static  void   kern_diff_pen_l1_mult(int n , float *d_w ,float * d_p  , float alpha ,
						 float weight, float * forreduce, float *mult ) {
  volatile  __shared__ double chache[WKSIZE] ;
  int gid;
  double toreduce=0.0;
  float isign=0.0f;
  float t;//,t1,t2,t3;
  int tid = threadIdx.x ;
  int gridsize = gridDim.x*blockDim.x; // int gridsize = WKSIZE*blockDim.x;
  gid = tid+ blockIdx.x*blockDim.x;

  while(gid<n) {
    t = d_w[gid]+alpha*d_p[gid];
    if(t>0.0f) {
      isign = -weight;
    }
    if (t<0.0f) {
      isign =  weight;
    }
    toreduce += isign *d_p[gid]*mult[gid] ;
    toreduce += -  ( (t==0.0f) )*weight *d_p[gid]*mult[gid]  ;
    gid+=gridsize;
  }

  chache[ threadIdx.x  ] = toreduce  ;
  __syncthreads () ;

  if(WKSIZE>=512) {  if(tid<256) { chache[tid] += chache [tid + 256]; } __syncthreads();  }
  if(WKSIZE>=256) {  if(tid<128) { chache[tid] += chache [tid + 128]; } __syncthreads();  }
  if(WKSIZE>=128) {  if(tid<64) { chache[tid]  += chache [tid + 64]; } __syncthreads();  }
  if(tid<32) {
    if(WKSIZE>=64) chache[tid]  += chache [tid + 32];
    if(WKSIZE>=32) chache[tid]  += chache [tid + 16];
    if(WKSIZE>=16) chache[tid]  += chache [tid + 8];
    if(WKSIZE>=8) chache[tid]  += chache [tid + 4];
    if(WKSIZE>=4) chache[tid]  += chache [tid + 2];
    if(WKSIZE>=2) chache[tid]  += chache [tid + 1];
  }
  if ( tid == 0 ) {
    forreduce[blockIdx.x] = chache [0] ;
  }
}

__global__  static  void   DDkern_cublasSdot(int n , float *d_w ,float * d_p  , double * DDforreduce ) {

  volatile  __shared__ double chache[WKSIZE] ;
  int gid;
  double toreduce=0.0;
  //  float isign=0.0f;
  //  float t;//,t1,t2,t3;
  int tid = threadIdx.x ;
  int gridsize = gridDim.x*blockDim.x; // int gridsize = WKSIZE*blockDim.x;
  gid = tid+ blockIdx.x*blockDim.x;

  while(gid<n) {
    toreduce += d_w [gid]*d_p[gid]  ;
    gid+=gridsize;
  }

  chache[ threadIdx.x  ] = toreduce  ;
  __syncthreads () ;

  if(WKSIZE>=512) {  if(tid<256) { chache[tid] += chache [tid + 256]; } __syncthreads();  }
  if(WKSIZE>=256) {  if(tid<128) { chache[tid] += chache [tid + 128]; } __syncthreads();  }
  if(WKSIZE>=128) {  if(tid<64) { chache[tid]  += chache [tid + 64]; } __syncthreads();  }
  if(tid<32) {
    if(WKSIZE>=64) chache[tid]  += chache [tid + 32];
    if(WKSIZE>=32) chache[tid]  += chache [tid + 16];
    if(WKSIZE>=16) chache[tid]  += chache [tid + 8];
    if(WKSIZE>=8) chache[tid]  += chache [tid + 4];
    if(WKSIZE>=4) chache[tid]  += chache [tid + 2];
    if(WKSIZE>=2) chache[tid]  += chache [tid + 1];
  }
  if ( tid == 0 ) {
    DDforreduce[blockIdx.x] = chache [0] ;
  }
}



__global__  static  void   DDkern_diff_pen_l1_mult(int n , float *d_w ,float * d_p  , float alpha ,
						   float weight, double * DDforreduce, float *mult ) {
  volatile  __shared__ double chache[WKSIZE] ;
  int gid;
  double toreduce=0.0;
  float isign=0.0f;
  float t;//,t1,t2,t3;
  int tid = threadIdx.x ;
  int gridsize = gridDim.x*blockDim.x; // int gridsize = WKSIZE*blockDim.x;
  gid = tid+ blockIdx.x*blockDim.x;

  while(gid<n) {
    t = d_w[gid]+alpha*d_p[gid];
    if(t>0.0f) {
      isign = -weight;
    }
    if (t<0.0f) {
      isign =  weight;
    }
    toreduce += isign *d_p[gid]*mult[gid] ;
    toreduce += -  ( (t==0.0f) )*weight *d_p[gid]*mult[gid]  ;
    gid+=gridsize;
  }

  chache[ threadIdx.x  ] = toreduce  ;
  __syncthreads () ;

  if(WKSIZE>=512) {  if(tid<256) { chache[tid] += chache [tid + 256]; } __syncthreads();  }
  if(WKSIZE>=256) {  if(tid<128) { chache[tid] += chache [tid + 128]; } __syncthreads();  }
  if(WKSIZE>=128) {  if(tid<64) { chache[tid]  += chache [tid + 64]; } __syncthreads();  }
  if(tid<32) {
    if(WKSIZE>=64) chache[tid]  += chache [tid + 32];
    if(WKSIZE>=32) chache[tid]  += chache [tid + 16];
    if(WKSIZE>=16) chache[tid]  += chache [tid + 8];
    if(WKSIZE>=8) chache[tid]  += chache [tid + 4];
    if(WKSIZE>=4) chache[tid]  += chache [tid + 2];
    if(WKSIZE>=2) chache[tid]  += chache [tid + 1];
  }
  if ( tid == 0 ) {
    DDforreduce[blockIdx.x] = chache [0] ;
  }
}




__global__  static  void   kern_complete_grad_l1_andsum(int n , float *d_w ,float * d_df , float * d_dw ,  float weight,
							float *forreduce) {
  int gid;
  float isign=0.0f;
  double toreduce=0.0;

  volatile __shared__ double chache[WKSIZE] ;

  gid = threadIdx.x + blockIdx.x*blockDim.x;
  int tid = threadIdx.x ;
  int gridsize = gridDim.x*blockDim.x; // int gridsize = WKSIZE*blockDim.x;

  while(gid<n) {
    toreduce += fabs(d_w[gid]);
    if(d_w[gid]>0.0f) {
      isign=+1.0f ;
    }
    if (d_w[gid]<0.0f) {
      isign=-1.0f ;
    }
    if (d_w[gid]==0.0f) {
      if( d_dw[gid] != 0.0f ) {
	isign=   copysignf( 1.0f, d_dw[gid] ) ;
      }
    }
    d_df[gid] = d_dw[gid]- isign*weight;
    gid += gridsize ;
  }


  chache[ threadIdx.x  ] = toreduce  ;
  __syncthreads () ;

  if(WKSIZE>=512) {  if(tid<256) { chache[tid] += chache [tid + 256]; } __syncthreads();  }
  if(WKSIZE>=256) {  if(tid<128) { chache[tid] += chache [tid + 128]; } __syncthreads();  }
  if(WKSIZE>=128) {  if(tid<64) { chache[tid]  += chache [tid + 64]; } __syncthreads();  }
  if(tid<32) {
    if(WKSIZE>=64) chache[tid]  += chache [tid + 32];
    if(WKSIZE>=32) chache[tid]  += chache [tid + 16];
    if(WKSIZE>=16) chache[tid]  += chache [tid + 8];
    if(WKSIZE>=8) chache[tid]  += chache [tid + 4];
    if(WKSIZE>=4) chache[tid]  += chache [tid + 2];
    if(WKSIZE>=2) chache[tid]  += chache [tid + 1];
  }
  if ( tid == 0 ) {
    forreduce[blockIdx.x] = chache [0] ;
  }
}



__global__  static  void kern_fill_invertiti_given_alpha(int todo , int done , int *d_invertiti, float *d_w, float *d_p,float alpha){
  int gid;
  float  inversione;
  gid = threadIdx.x + blockIdx.x*blockDim.x+done;
  if(gid<todo+done ) {
    if(d_p[gid]==0.0f) {
      inversione =  CUDART_INF_F ;
    }
    if(d_p[gid]!=0.0f){
      inversione = -d_w[gid]/d_p[gid];
    }
    if(inversione<0) inversione = CUDART_INF_F ;
    int invertito=0;
    if(alpha>=inversione) {
      invertito=1;
    }
    d_invertiti[gid]=min( ( d_invertiti[gid]+  invertito)*invertito,10) ;
  }
}
void call_kern_fill_invertiti_given_alpha( int ncomps, int *d_invertiti, float *d_w, float *d_p , float alpha)  {
  int totpatch = ncomps;
  dim3 blk = dim3( WKSIZE , 1 , 1 );
  int done=0;
  while(done<totpatch) {
    // if(done) printf("WARNING by pieces line %d \n", __LINE__);

    int nb =   min(  iDivUp(totpatch-done, WKSIZE), 0xFFFF )  ;
    int todo = min( nb* WKSIZE ,totpatch -done   ) ;
    dim3 grd = dim3( nb , 1, 1 );
    kern_fill_invertiti_given_alpha<<<grd,blk>>>( todo, done,  d_invertiti, d_w, d_p, alpha);
    done+=todo;
  }
}



__global__  static  void kern_createapply_projector_pass2(int todo , int done , float * d_w, float *d_p, float *d_dw,
							  int *d_invertiti, float weight,
							  float *d_dp, float *d_dfpro, float *d_df ){
  int gid;
  // float grad;// ,val, P;
  // int segno;
  gid = threadIdx.x + blockIdx.x*blockDim.x+done;
  int proiettore=1;
  if(gid<todo+done ) {

    proiettore = 1- d_invertiti[gid];

    if(proiettore==0) {
      d_dfpro[gid] = 0.0f;
    } else {
      d_dfpro[gid] =  d_df[gid]   ;
    }

    if(proiettore==0 || d_w[gid]==0.0f) {
      d_p[gid] = 0.0f;
    }
  }
}



__global__  static  void kern_createapply_projector_pass2_coalesced(int n , float * d_w, float *d_p, float *d_dw,
								    int *d_invertiti, float weight,
								    float *d_dp, float *d_dfpro, float *d_df,
								    float *forreduce, float * forreduce1 ){
  int gid;
  volatile __shared__ double chache[WKSIZE] ;
  volatile __shared__ double chache1[WKSIZE] ;

  float red=0.0f, red1=0.0f;
  int tid = threadIdx.x ;
  int gridsize = gridDim.x*blockDim.x; // int gridsize = WKSIZE*blockDim.x;
  gid = threadIdx.x + blockIdx.x*blockDim.x;
  int proiettore=1;

  while(gid<n) {
    proiettore = 1- d_invertiti[gid];

    if(proiettore==0) {
      d_dfpro[gid] = 0.0f;
    } else {
      d_dfpro[gid] =  d_df[gid]   ;
    }

    if(proiettore==0 || d_w[gid]==0.0f) {
      d_p[gid] = 0.0f;
    }

    red  += d_dp[gid] *  d_dfpro[gid]  ;
    red1 += d_dp[gid] *  d_p[gid]  ;

    gid += gridsize ;
  }

  chache [ threadIdx.x  ] =  red  ;
  chache1[ threadIdx.x  ] =  red1 ;
  __syncthreads () ;

  if(WKSIZE>=512) {  if(tid<256) { chache[tid] += chache [tid + 256]; chache1[tid] += chache1 [tid + 256]; } __syncthreads();  }
  if(WKSIZE>=256) {  if(tid<128) { chache[tid] += chache [tid + 128]; chache1[tid] += chache1 [tid + 128]; } __syncthreads();  }
  if(WKSIZE>=128) {  if(tid<64)  { chache[tid]  += chache [tid + 64]; chache1[tid] += chache1 [tid + 64]; } __syncthreads();  }
  if(tid<32) {
    if(WKSIZE>=64) { chache[tid]  += chache [tid + 32]; chache1[tid]  += chache1 [tid + 32];}
    if(WKSIZE>=32) { chache[tid]  += chache [tid + 16]; chache1[tid]  += chache1 [tid + 16];}
    if(WKSIZE>=16) { chache[tid]  += chache [tid + 8];  chache1[tid]  += chache1 [tid + 8]; }
    if(WKSIZE>=8)  { chache[tid]  += chache [tid + 4];  chache1[tid]  += chache1 [tid + 4]; }
    if(WKSIZE>=4)  { chache[tid]  += chache [tid + 2];  chache1[tid]  += chache1 [tid + 2]; }
    if(WKSIZE>=2)  { chache[tid]  += chache [tid + 1];  chache1[tid]  += chache1 [tid + 1]; }
  }
  if ( tid == 0 ) {
    forreduce[blockIdx.x] = chache [0] ;
    forreduce1[blockIdx.x] = chache1 [0] ;
  }


  int i  = blockDim.x / 2 ;
  while ( i!=0 )    {
    if (  threadIdx.x  < i ) {
      chache [ threadIdx.x ] += chache  [ threadIdx.x  + i] ;
      chache1[ threadIdx.x ] += chache1 [ threadIdx.x  + i] ;
    }
    __syncthreads () ;
    i/=2 ;
  }
  if ( threadIdx.x == 0 ) {
    forreduce [blockIdx.x] = chache  [0] ;
    forreduce1[blockIdx.x] = chache1 [0] ;
  }
}



__global__  static  void kern_createapply_projector_pass1(int todo , int done , float * d_w, float *d_p, float *d_dw,
							  int *d_invertiti, float weight,
							  float *d_dp, float *d_dfpro, float *d_df, float L ){
  int gid;
  float grad;// ,val, P;
  // int segno;
  // L=0;
  gid = threadIdx.x + blockIdx.x*blockDim.x+done;
  int proiettore=1;

  if(gid<todo+done ) {

    // segno=1;
    grad = d_dw[gid];
    // val  = d_w[gid];
    // P    = d_p[gid];

    if ( d_w[gid]==0.0f) {
      if(abs(grad)<weight) {
	proiettore=0;
      }
      // segno=0;
    }
    if (d_invertiti[gid]>0 ) {
      if(abs(grad)+abs(d_w[gid]*L)<weight) {
        proiettore=0;
      }
      // if(val<0) segno=-1;
    }

    if(proiettore==0) {
      //  d_dw[gid] = grad -  segno*P ;
      d_w[gid] = 0.0f;
      // d_dfpro[gid] = 0.0f;
      d_p[gid] = 0.0f;
    }  else {
      // d_dfpro[gid] =  d_df[gid]   ;
    }
    d_invertiti[gid]=1-proiettore;
  }
}


void call_kern_createapply_projector( int ncomps, float * d_w, float *d_p, float *d_dw,
				      int *d_invertiti, float weight,
				      float *d_dp, float *d_dfprojected, float *d_df,
				      float L , int pass=1)  {
  int totpatch = ncomps;
  dim3 blk = dim3( WKSIZE , 1 , 1 );
  int done=0;
  while(done<totpatch) {
    //        if(done) printf("WARNING by pieces line %d \n", __LINE__);

    //    if(done) printf("WARNING by pieces line %d \n", __LINE__);
    int nb =   min(  iDivUp(totpatch-done, WKSIZE), 0xFFFF )  ;
    int todo = min( nb* WKSIZE ,totpatch -done   ) ;
    dim3 grd = dim3( nb , 1, 1 );
    if(pass==1) {
      kern_createapply_projector_pass1<<<grd,blk>>>( todo, done, d_w, d_p, d_dw,
						     d_invertiti,  weight,
						     d_dp, d_dfprojected,  d_df,  L);
    } else {
      kern_createapply_projector_pass2<<<grd,blk>>>( todo, done, d_w, d_p, d_dw,
						     d_invertiti,  weight,
						     d_dp, d_dfprojected, d_df);
    }
    done+=todo;
  }
}



//--------------------------------------------------------------------------------------
//------- Conjugate Subgradient kernels ------------------------------------------------
//--------------------------------------------------------------------------------------


///Replace "A" by A*B element-wise
__global__ void csg_kern_eltw_product(
				      float* A,
				      float* B,
				      int sizeX,
				      int sizeY)
{
  int gidx = threadIdx.x + blockIdx.x*blockDim.x;
  int gidy = threadIdx.y + blockIdx.y*blockDim.y;
  int i = gidy*sizeX+gidx;
  if (gidx < sizeX && gidy < sizeY) {
    A[i] *= B[i];
  }
}
///Replace "A" by A/B element-wise
__global__ void csg_kern_eltw_division(
				       float* A,
				       float* B,
				       int sizeX,
				       int sizeY)
{
  int gidx = threadIdx.x + blockIdx.x*blockDim.x;
  int gidy = threadIdx.y + blockIdx.y*blockDim.y;
  int i = gidy*sizeX+gidx;
  if (gidx < sizeX && gidy < sizeY) {
    A[i] /= B[i];
  }
}
__global__ void csg_kern_eltw_add_constant(
					   float* A,
					   float b,
					   int sizeX)
{
  int gidx = threadIdx.x + blockIdx.x*blockDim.x;
  int i = gidx;
  while (i < sizeX ) {
    A[i] += b;
    i +=  gridDim.x*blockDim.x ; 
  }
}

__global__ void csg_kern_eltw_multiplier(
					 float* minus_grad_tot,
					 float* Qgrad,
					 float* mult,
					 float* sticker,
					 int numels)
{
  int i = threadIdx.x + blockIdx.x*blockDim.x;
  if (i < numels) {
    float m = mult[i]*sticker[i];
    minus_grad_tot[i] *= m;
    Qgrad[i] *= m;
  }
}
__global__ void csg_kern_eltw_multiplier2(
					  float* minus_grad_tot,
					  float* Qgrad,
					  float* mult,
					  float* sticker,
					  int size,
					  int done)
{
  int i = threadIdx.x + blockIdx.x*blockDim.x; // +done;
  while(i < size) {
    float m = mult[i]*sticker[i];
    minus_grad_tot[i] *= m;
    Qgrad[i] *= m;
    i +=  blockDim.x * gridDim.x  ; 
  }
}

__global__ void csg_kern_multiplier_fromp2mp(
					     float* d_mp,
					     float* d_p,
					     float* mult,
					     int numels,
					     int done)
{
  int i = threadIdx.x + blockIdx.x*blockDim.x;
  while (i < numels) {
    float m = mult[i];
    d_mp[i] = m*d_p[i];
    i +=  blockDim.x * gridDim.x ; 
  }
}
__global__ void csg_kern_multiplier_fromp2mpinplace(
						    float* d_p,
						    float* mult,
						    int numels,
						    int done)
{
  int i = threadIdx.x + blockIdx.x*blockDim.x;
  while (i < numels) {
    float m = mult[i];
    d_p[i] = m*d_p[i];
    i +=  blockDim.x * gridDim.x  ; 
  }
}

/**
 * @brief csg_call_multiplier_kernel : updates gradient by multiplying it by "mult" and "sticker" eltw.
 * @param minus_grad_tot : -grad_f (quadratic part + L1 part)
 * @param Qgrad : quadratic part of the gradient
 * @param mult : multiplier (variable preconditioner)
 * @param sticker : another multiplier
 * @param size_w : number of elements
 */
void csg_call_multiplier_fromp2mp_kernel(float * d_mp, float * d_p, float *mult,  int size_w) {
  dim3 blk = dim3(WKSIZE, 1, 1);
  dim3 grd = dim3(iDivUp(size_w, WKSIZE), 1, 1);
  int done = 0;
  // while (done < size_w) {
  
  int n_blocks =   min(  iDivUp(size_w-done, WKSIZE), 65535 )  ;
  // int current_size = min( n_blocks* WKSIZE ,size_w-done   ) ;
  grd = dim3(n_blocks,1,1);
  csg_kern_multiplier_fromp2mp<<<grd,blk>>>(d_mp, d_p, mult,size_w  , done);
  // done+=current_size;
  // }
}
void csg_call_multiplier_fromp2mpinplace_kernel( float * d_p, float *mult,  int size_w) {
  dim3 blk = dim3(WKSIZE, 1, 1);
  dim3 grd = dim3(iDivUp(size_w, WKSIZE), 1, 1);
  int done = 0;
  // while (done < size_w) {
  int n_blocks =   min(  iDivUp(size_w-done, WKSIZE), 65535 )  ;
  // int current_size = min( n_blocks* WKSIZE ,size_w-done   ) ;
  grd = dim3(n_blocks,1,1);
  csg_kern_multiplier_fromp2mpinplace<<<grd,blk>>>( d_p, mult,size_w  , done);
  // done+=current_size;
  //}
}


void csg_call_multiplier_kernel(float* minus_grad_tot, float* Qgrad, float* mult, float* sticker, int size_w) {

  dim3 blk = dim3(WKSIZE, 1, 1);
  dim3 grd = dim3(iDivUp(size_w, WKSIZE), 1, 1);
  //  csg_kern_eltw_multiplier<<<grd,blk>>>(minus_grad_tot, Qgrad, mult, sticker, 65536, 241);

  //limits the number of blocks launched to 65535 (for compute capabilities < 3.0)
  int done = 0;
  // while (done < size_w) {
  int n_blocks =   min(  iDivUp(size_w-done, WKSIZE), 65535 )  ;
  // int current_size = min( n_blocks* WKSIZE ,size_w-done   ) ;
  grd = dim3(n_blocks,1,1);
  csg_kern_eltw_multiplier2<<<grd,blk>>>(minus_grad_tot, Qgrad, mult, sticker,size_w , done);
  //  done+=current_size;
  // }
}




/**
 * Nvidia Cuda samples : "reduce5" (v6, 2014)
 * Usage (ex. with 128) :
 *      int maxThreads=128; //threads per block
 int threads=(numels<maxThreads)?numels:maxThreads;
 dim3 gridSize(iDivUp(numels,128),1,1);
 dim3 blockSize(128,1,1);
 int smemSize=threads*sizeof(float);
 myreduce_pass1<128><<<gridSize,blockSize, smemSize>>>(in, out, numels);
 *
 */

template <unsigned int blockSize>
__global__ void myreduce_pass1(float *g_idata, float *g_odata, int n) {
  extern __shared__ float sdata[];
  // perform first level of reduction,
  // reading from global memory, writing to shared memory
  unsigned int tid = threadIdx.x;
  unsigned int i = blockIdx.x*(blockSize*2) + threadIdx.x;
  float mySum = (i < n) ? g_idata[i] : 0;
  if (i + blockSize < n)
    mySum += g_idata[i+blockSize];
  sdata[tid] = mySum;
  __syncthreads();
  // do reduction in shared mem
  if (blockSize >= 512)  {
    if (tid < 256)    {
      sdata[tid] = mySum = mySum + sdata[tid + 256];
    }
    __syncthreads();
  }
  if (blockSize >= 256)  {
    if (tid < 128)    {
      sdata[tid] = mySum = mySum + sdata[tid + 128];
    }
    __syncthreads();
  }
  if (blockSize >= 128)  {
    if (tid <  64)    {
      sdata[tid] = mySum = mySum + sdata[tid +  64];
    }
    __syncthreads();
  }
  if (tid < 32)  {
    // now that we are using warp-synchronous programming (below)
    // we need to declare our shared memory volatile so that the compiler
    // doesn't reorder stores to it and induce incorrect behavior.
    volatile float *smem = sdata;
    if (blockSize >=  64) {
      smem[tid] = mySum = mySum + smem[tid + 32];
    }
    if (blockSize >=  32) {
      smem[tid] = mySum = mySum + smem[tid + 16];
    }
    if (blockSize >=  16) {
      smem[tid] = mySum = mySum + smem[tid +  8];
    }
    if (blockSize >=   8) {
      smem[tid] = mySum = mySum + smem[tid +  4];
    }
    if (blockSize >=   4) {
      smem[tid] = mySum = mySum + smem[tid +  2];
    }
    if (blockSize >=   2) {
      smem[tid] = mySum = mySum + smem[tid +  1];
    }
  }
  // write result for this block to global mem
  if (tid == 0) g_odata[blockIdx.x] = sdata[0];
}


/**
   Simple kernel : last pass of the (sum) reduction
   This sums the "n" elements of g_data, knowing that n is small enough (n < blockdim)
   CAUTION : "n" MUST be < block size !
*/

template <unsigned int blockSize>
__global__ void myreduce_pass2(float *g_data, int n) {
  int gidx = threadIdx.x + blockIdx.x*blockDim.x;
  int tid = threadIdx.x;
  extern __shared__ float smem[];
  if (gidx < n) {
    smem[tid] = g_data[gidx];
    int n2 = n;
    //unroll for more performances
    while (n2 > 1) {
      if (n2 & 1 && tid == n2-2) {
        smem[tid] += smem[tid+1];
        __syncthreads();
      }
      n2 >>=1;
      if (gidx < n2) {
        smem[tid] += smem[tid+n2];
      }
      __syncthreads();
    }
    if (tid == 0) g_data[0] = smem[0];
  }
}



/**
 * @brief sum_reduce : sums the elements of an array using parallel reductions.
 * @param idata : input array, data we want to calculate the sum of
 * @param odata : temporary array of size (at least) "n/blockdim". Modified by the function !
 * @param n : number of elements of the input data.
 * @param res : pointer on host memory.
 *    if res != NULL, the result is copied from the GPU to "res". This operation is very slow.
 *    if res == NULL, no copy is done. The result can be retrieved on the gpu in odata[0].
 *
 */
#define RED_BLKSIZE 128
void sum_reduce(float* idata, float* odata, int n, float* res = NULL) {
  int threads=(n<RED_BLKSIZE)?n:RED_BLKSIZE;
  dim3 gridSize(iDivUp(n,RED_BLKSIZE),1,1);
  dim3 blockSize(RED_BLKSIZE,1,1);
  int smemSize=threads*sizeof(float);
  //Stage 1 : reduce the array idata to odata
  int newsize = n;
  myreduce_pass1<RED_BLKSIZE><<<gridSize,blockSize, smemSize>>>(idata, odata, n);
  newsize /= RED_BLKSIZE;
  while (newsize > RED_BLKSIZE) {
    gridSize = dim3(iDivUp(newsize,RED_BLKSIZE),1,1);
    myreduce_pass1<RED_BLKSIZE><<<gridSize,blockSize, smemSize>>>(odata, odata, newsize);
    newsize /= RED_BLKSIZE;
  }
  //Stage 2 : sum a few numbers (< blocksize) of elements with another kernel
  int npass = (int) ilog2(n)/ilog2(RED_BLKSIZE);
  int remaining = ((n/ipow(RED_BLKSIZE,npass)) % RED_BLKSIZE) +1;
  gridSize = dim3(1,1,1);
  myreduce_pass2<RED_BLKSIZE><<<gridSize,blockSize, smemSize>>>(odata, remaining);
  if (res != NULL) cudaMemcpy(res, odata, sizeof(float), cudaMemcpyDeviceToHost);
}

#undef RED_BLKSIZE



//(mult, p, dp, w_old) --> (Qgrad, w, w_old, w_dummy, mult, sticker, p_old, dp_old)
__global__ void csg_kern_update_variables(
					  float* mult,    //preconditioner matrix
					  float* w,       //(new) coeff. vector
					  float* w_old,   //
					  float* p,       //(new) descent direction
					  float* p_old,   //
					  float* dp,      //(new) gradient of d.d ("P^T P p")
					  float* dp_old,  //
					  float* sticker, //vector of variable that shall be "frozen" for the next calculation
					  float* w_dummy, //temp. matrix containing values of "w" multiplied by "mult" before calling calculate_grad_error
					  float* Qgrad,   //quadratic part of the gradient of F = f_smooth + L1
					  float alpha,    //line search step
					  float weight,     //L1 penalization
					  float shrink,   //
					  float increase, //
					  int step,
					  int sizeX)
{
  float qgrad;
  int i = threadIdx.x + blockIdx.x*blockDim.x;
  while (i < sizeX) {
    int small = 0, nonsmall = 0, sticker_val = 1;
    float val = 0.0f, mvar = 1;
    float mult_val = mult[i];

    /// qgrad    =   Qgrad[i]  +  alpha*dp[i]*  mult_val ; //  *  mult_val ;// dans le referentiel adaptative
    qgrad    =   Qgrad[i]  +  alpha*dp[i]  ; //  *  mult_val ;// dans le referentiel adaptative   RRRRR

    w[i] = w_old[i] + alpha*p[i];

    if ((fabsf(qgrad) < weight*mult_val) && (w[i]*w_old[i] < 0)) small = 1;
    if (mult_val < 1) nonsmall = 1;
    nonsmall *= 1-small;
    val = mult_val*(1-shrink*small + nonsmall*increase/(1+step/50000000)); //FIXME
    if (val > 1) val = 1;
    mult[i] = val;
    mvar = val/mult_val;
    qgrad    *=  mvar ; // dans le nouveau referentiel adaptative

    if ((fabsf(qgrad) < weight*val) && fabsf(w[i]*val) < /*1.0e-10*/weight/100.0f) sticker_val = 0; //FIXME : how to choose tol ?
    w[i] = w[i] * sticker_val;
    w_old[i] = w[i]/mvar * sticker_val;
    p_old[i] = p[i]  * sticker_val;  // p est encore la direction dans le variable d 'avant,* mvar
    // on la transforme en p_old qui l 'est dans le nouvelles   !!!! mvar
    // dp_old[i] = dp[i] * mvar * sticker_val;
    // dp_old[i] = dp[i] * val*sticker_val; // ==============================>>>>>>>>  ATTENTION
    dp_old[i] = dp[i] * mvar *sticker_val; // ==============================>>>>>>>>  ATTENTION  RRRRRRRRRRR
    // a l origin dp est la vitesse de variation du vrai gradient
    sticker[i] = sticker_val;
    //gradient also needs to be updated after this variable substitution
    //this is done after a call to calculate_grad_error()
    w_dummy[i] = w_old[i] * val;
    Qgrad[i]  =   qgrad / val  ;  // dans le referentiel absolu ( comme si on l avait calcule')
    i +=   blockDim.x * gridDim.x ; 
  }
}



/// reduction to calculate beta_num (from minus_grad_tot and dp_old), beta_denom (from dp_old and p_old), L1_norm (from w_dummy) and sparsity (from mult)
/// the temporary storage variables are p, sticker, sarray1, sarray2
//(mult, sticker, p, dp_old, w_dummy, [minus_grad_tot, Qgrad]) --> (p, sticker, sarray1, sarray2, [minus_grad_tot, Qgrad])
__global__ void csg_kern_update_direction_pass1(
						float* minus_grad_tot,
						float* Qgrad,
						int first_launch,
						float* mult,
						float* sticker,
						float* p_old,
						float* dp_old,
						float* w_dummy,
						float* p,
						float* sarray1, //storage array for "L1_norm"
						float* sarray2, //storage array for "sparsity"
						int n)
{

  volatile __shared__ float sdata1[WKSIZE];
  volatile __shared__ float sdata2[WKSIZE];
  volatile __shared__ float sdata3[WKSIZE];
  volatile __shared__ float sdata4[WKSIZE];

  unsigned int tid = threadIdx.x;
  unsigned int i = blockIdx.x*(WKSIZE*2) + threadIdx.x;
  unsigned int gridSize = WKSIZE*2* gridDim.x;
  float mySum1 = 0, mySum2 = 0, mySum3 = 0, mySum4 = 0;
  sdata1[tid] = 0;
  sdata2[tid] = 0;
  sdata3[tid] = 0;
  sdata4[tid] = 0;

  //serial part, global memory access
  while (i < n) {
    if (first_launch) {
      //update grad and Qgrad
      float m = mult[i]*sticker[i];
      minus_grad_tot[i] *= m;
      Qgrad[i] *= m;
      //---
      mySum1 += dp_old[i]*minus_grad_tot[i];
      mySum2 += dp_old[i]*p_old[i];
      mySum3 += w_dummy[i];
      mySum4 += mult[i];
      if (i + WKSIZE < n) {
        mySum1 += dp_old[i+WKSIZE]*minus_grad_tot[i+WKSIZE];
        mySum2 += dp_old[i+WKSIZE]*p_old[i+WKSIZE];
        mySum3 += w_dummy[i+WKSIZE];
        mySum4 += mult[i+WKSIZE];
      }
    }
    else {
      mySum1 += p[i];
      mySum2 += sticker[i];
      mySum3 += sarray1[i];
      mySum4 += sarray2[i];
      if (i + WKSIZE < n) {
        mySum1 += p[i+WKSIZE];
        mySum2 += sticker[i+WKSIZE];
        mySum3 += sarray1[i+WKSIZE];
        mySum4 += sarray2[i+WKSIZE];
      }
    }
    i += gridSize;
  }
  sdata1[tid] = mySum1;
  sdata2[tid] = mySum2;
  sdata3[tid] = mySum3;
  sdata4[tid] = mySum4;
  __syncthreads();
#if WKSIZE >= 512
  if (tid < 256) {
    sdata1[tid] = mySum1 = mySum1 + sdata1[tid + 256];
    sdata2[tid] = mySum2 = mySum2 + sdata2[tid + 256];
    sdata3[tid] = mySum3 = mySum3 + sdata3[tid + 256];
    sdata4[tid] = mySum4 = mySum4 + sdata4[tid + 256];
  }
  __syncthreads();
#endif
#if WKSIZE >= 256
  if (tid < 128)    {
    sdata1[tid] = mySum1 = mySum1 + sdata1[tid + 128];
    sdata2[tid] = mySum2 = mySum2 + sdata2[tid + 128];
    sdata3[tid] = mySum3 = mySum3 + sdata3[tid + 128];
    sdata4[tid] = mySum4 = mySum4 + sdata4[tid + 128];
  }
  __syncthreads();
#endif
#if WKSIZE >= 128
  if (tid <  64)    {
    sdata1[tid] = mySum1 = mySum1 + sdata1[tid +  64];
    sdata2[tid] = mySum2 = mySum2 + sdata2[tid +  64];
    sdata3[tid] = mySum3 = mySum3 + sdata3[tid +  64];
    sdata4[tid] = mySum4 = mySum4 + sdata4[tid +  64];
  }
  __syncthreads();
#endif
  if (tid < 32)  {
    volatile float *smem1 = sdata1;
    volatile float *smem2 = sdata2;
    volatile float *smem3 = sdata3;
    volatile float *smem4 = sdata4;
#if WKSIZE >= 64
    smem1[tid] = mySum1 = mySum1 + smem1[tid + 32];
    smem2[tid] = mySum2 = mySum2 + smem2[tid + 32];
    smem3[tid] = mySum3 = mySum3 + smem3[tid + 32];
    smem4[tid] = mySum4 = mySum4 + smem4[tid + 32];
#endif
#if WKSIZE >= 32
    smem1[tid] = mySum1 = mySum1 + smem1[tid + 16];
    smem2[tid] = mySum2 = mySum2 + smem2[tid + 16];
    smem3[tid] = mySum3 = mySum3 + smem3[tid + 16];
    smem4[tid] = mySum4 = mySum4 + smem4[tid + 16];
#endif
#if WKSIZE >= 16
    smem1[tid] = mySum1 = mySum1 + smem1[tid +  8];
    smem2[tid] = mySum2 = mySum2 + smem2[tid +  8];
    smem3[tid] = mySum3 = mySum3 + smem3[tid +  8];
    smem4[tid] = mySum4 = mySum4 + smem4[tid +  8];
#endif
#if WKSIZE >= 8
    smem1[tid] = mySum1 = mySum1 + smem1[tid +  4];
    smem2[tid] = mySum2 = mySum2 + smem2[tid +  4];
    smem3[tid] = mySum3 = mySum3 + smem3[tid +  4];
    smem4[tid] = mySum4 = mySum4 + smem4[tid +  4];
#endif
#if WKSIZE >= 4
    smem1[tid] = mySum1 = mySum1 + smem1[tid +  2];
    smem2[tid] = mySum2 = mySum2 + smem2[tid +  2];
    smem3[tid] = mySum3 = mySum3 + smem3[tid +  2];
    smem4[tid] = mySum4 = mySum4 + smem4[tid +  2];
#endif
#if WKSIZE >= 2
    smem1[tid] = mySum1 = mySum1 + smem1[tid +  1];
    smem2[tid] = mySum2 = mySum2 + smem2[tid +  1];
    smem3[tid] = mySum3 = mySum3 + smem3[tid +  1];
    smem4[tid] = mySum4 = mySum4 + smem4[tid +  1];
#endif
  }
  // write result for this block to global mem
  if (tid == 0) {
    p[blockIdx.x] = sdata1[0]; //beta_num
    sticker[blockIdx.x] = sdata2[0]; //beta_denom
    sarray1[blockIdx.x] = sdata3[0]; //L1_norm
    sarray2[blockIdx.x] = sdata4[0]; //sparsity
  }
}





//(p, sticker, sarray1, sarray2, minus_grad_tot, p_old) --> (p, sarray1)
__global__ void csg_kern_update_direction_pass2(
						float* sticker,
						float* sarray1,
						float* sarray2,
						float* p,
						float* p_old,
						float* minus_grad_tot,
						int n,
						int newn)
{
  int i = threadIdx.x + blockIdx.x*blockDim.x;
  int tid = threadIdx.x;
  float beta_num = 0;
  float beta_denom = 0;
  float l1norm = 0;
  float spars = 0;
  //pre-fetch in shared mem
  __shared__ float p_shared[WKSIZE];
  __shared__ float sticker_shared[WKSIZE];
  __shared__ float sarr1_shared[WKSIZE];
  __shared__ float sarr2_shared[WKSIZE];
  if (i < newn) {
    p_shared[tid] = p[i];
    sticker_shared[tid] = sticker[i];
    sarr1_shared[tid] = sarray1[i];
    sarr2_shared[tid] = sarray2[i];
  }
  __syncthreads();
  //----
  int k;
  if (i == 0) {
    for (k = 0; k < newn; k++) beta_num += p_shared[k];
    p_shared[0] = beta_num;
  }
  if (i == 1) {
    for (k = 0; k < newn; k++) beta_denom += sticker_shared[k];
    sticker_shared[0] = beta_denom;
  }
  if (i == 2) {
    for (k = 0; k < newn; k++) l1norm += sarr1_shared[k];
    sarray1[1] = l1norm;
  }
  if (i == 3) {
    for (k = 0; k < newn; k++) spars += sarr2_shared[k];
    sarray1[2] = spars;
  }
  __syncthreads();
  if (i == 0) {
    if (p_shared[0]/sticker_shared[0] < 0) sarray1[0] = 0;
    else sarray1[0] = p_shared[0]/sticker_shared[0]; //beta_cg
  }
  __syncthreads(); //Warning : not sure if all threads in the grid are sync here ! do it in another kernel ?
  //update descent direction
  if (i < n) {
    p[i] = sarray1[0]*p_old[i] + minus_grad_tot[i];
  }
}

//
void csg_update_direction(
			  float* minus_grad_tot,
			  float* Qgrad,
			  float* mult,
			  float* sticker,
			  float* p_old,
			  float* dp_old,
			  float* w_dummy,
			  float* p,
			  float* beta_cg,
			  float* L1_norm,
			  float* sparsity,
			  int n)
{

  dim3 grd, blk;

  grd = dim3(iDivUp(n,WKSIZE),1,1);
  blk = dim3(WKSIZE,1,1);
  //storage arrays (needs at most n/WKSIZE elements)
  float* sarray1 = p + n/2;
  float* sarray2 = sticker + n/2;

  //first launch of this kernel : update gradients and to the first stage of reduction
  csg_kern_update_direction_pass1<<<grd,blk>>>(minus_grad_tot, Qgrad, 1, mult, sticker, p_old, dp_old, w_dummy, p, sarray1, sarray2, n);
  //now the storage arrays are reduced to a size n/WKSIZE/2. One needs others launchs to reduce further
  int newn = n/WKSIZE/2 +1;
  while (newn > WKSIZE/2) {
    grd = dim3(iDivUp(newn,WKSIZE),1,1);
    csg_kern_update_direction_pass1<<<grd,blk>>>(minus_grad_tot, Qgrad, 0, mult, sticker, p_old, dp_old, w_dummy, p, sarray1, sarray2, n);
    newn = newn/WKSIZE/2 +1;
  }

  //now we have a small number of elements in the storage arrays. Sum them with a simple kernel
  grd = dim3(iDivUp(n,WKSIZE),1,1);
  csg_kern_update_direction_pass2<<<grd,blk>>>(sticker, sarray1, sarray2, p, p_old, minus_grad_tot, n, newn);

  float* tmp = (float*) calloc(3,sizeof(float));
  cudaMemcpy(tmp, sarray1, 3*sizeof(float), cudaMemcpyDeviceToHost);
  *beta_cg = tmp[0];
  *L1_norm = tmp[1];
  *sparsity = tmp[2];

  free(tmp);
}





__global__ void csg_kern_update_conjugate_direction(
						    float* p,
						    float* p_old,
						    float* minus_grad_tot,
						    float* tmp1,
						    int sizeX,
						    int sizeY)
{
  int gidx = threadIdx.x + blockIdx.x*blockDim.x;
  int gidy = threadIdx.y + blockIdx.y*blockDim.y;
  int i = gidy*sizeX+gidx;
  if (gidx < sizeX && gidy < sizeY) {
    float beta = -tmp1[0]/p[0]; //TODO : improve this memory access
    if (beta < 0) beta = 0.0f;
    p[i] = minus_grad_tot[i] + beta*p_old[i];
  }
}


///p = -grad*mult *(1-numpy.equal(x,0))
__global__ void csg_kern_restart_direction(
					   float* p,
					   float* grad,
					   float* mult,
					   float* w,
					   int sizeX,
					   int sizeY)
{
  int gidx = threadIdx.x + blockIdx.x*blockDim.x;
  int gidy = threadIdx.y + blockIdx.y*blockDim.y;
  int i = gidy*sizeX+gidx;
  if (gidx < sizeX && gidy < sizeY) {
    if (w[i] != 0) p[i] = grad[i]*mult[i]; //attention, what is called "grad" is actually "-grad_tot" !!
    else p[i] = 0;
    w[i] /= mult[i];
  }
}


/**
   Performs :
   x = y + alpha * p
   x = soft_threshold(x, beta/Lip)
   (t = (1+sqrt(4*told*told+1))/2 : already done)
   y = x+(x-xold)*(told-1)/t
**/
__global__ void csg_kern_fista_update_variables(
						float* x,
						float* x_old,
						float* y,
						float* grad,
						float t,
						float t_old,
						float alpha,
						float beta_L,
						int sizeX,
						int sizeY)
{
  int gidx = threadIdx.x + blockIdx.x*blockDim.x;
  int gidy = threadIdx.y + blockIdx.y*blockDim.y;
  int i = gidy*sizeX+gidx;
  float xval;
  if (gidx < sizeX && gidy < sizeY) {
    xval = y[i] - alpha*grad[i];
    xval = copysignf(max(fabsf(xval) - beta_L, 0.0f), xval);
    x[i] = xval;
    y[i] = xval + (t_old-1)/t*(xval-x_old[i]);
  }
}


//resAB = dot(A,B) ; and possibly resCD = dot(C,D)
void csg_dotp(float* A, float* B, float* resAB, int n, float* C = NULL, float* D = NULL, float* resCD = NULL) {
  float acc1 = 0;
  if (C != NULL) {
    float acc2 = 0;
    for (int i=0; i < n; i++) {
      acc1 += A[i]*B[i];
      acc2 += C[i]*D[i];
    }
    *resAB = acc1;
    *resCD = acc2;
    return;
  }
  else {
    for (int i=0; i<n; i++) {
      acc1 += A[i]*B[i];
    }
    *resAB = acc1;
    return;
  }
}

void csg_mult(float* minus_grad_tot_rings, float* drings, float* mult_rings, float* sticker_rings, int num_bins) {
  for (int i=0; i<num_bins; i++) {
    float val = mult_rings[i]*sticker_rings[i];
    minus_grad_tot_rings[i] *= val;
    drings[i] *= val;
  }
}

float csg_sasum(float* A, int n) {
  float res = 0;
  for (int i=0; i<n; i++) res += fabsf(A[i]);
  return res;
}


void csg_update_rings(
		      float* rings,
		      float* rings_old,
		      float* drings,
		      float* p_rings,
		      float* p_rings_old,
		      float* dp_rings,
		      float* dp_rings_old,
		      float* mult_rings,
		      float* sticker_rings,
		      float* rings_dummy,
		      float alpha,
		      float weight,
		      float shrink,
		      float increase,
		      int step,
		      int n)
{
  float dring_tmp;
  for (int i=0; i<n; i++) {
    int small = 0, nonsmall = 0, sticker_val = 1;
    float val = 0.0f, mvar = 1, mult_val;
    mult_val = mult_rings[i];

    dring_tmp =  drings[i]+  alpha*dp_rings[i] ;  // referentiel adaptative      //* mult_val

    rings[i] = rings_old[i] + alpha*p_rings[i];

    if ((fabsf(dring_tmp) < weight*mult_val) && (rings[i]*rings_old[i] < 0)) small = 1;
    if (mult_val < 1) nonsmall = 1;
    nonsmall *= 1-small;
    val = mult_val*(1-shrink*small + nonsmall*increase/(1+step/50000000)); //FIXME
    if (val > 1) val = 1;
    mult_rings[i] = val;
    //if ((fabsf(drings[i]) < weight*val) && fabsf(rings[i]*val) < /*1.0e-10*/weight/1000.0f) sticker_val = 0;
    mvar = val/mult_val;
    dring_tmp    *=  mvar ; // dans le nouveau referentiel adaptative

    if ((fabsf(dring_tmp) < weight*val) && fabsf(rings[i]*val) < /*1.0e-10*/weight/10000000.0f) sticker_val = 0;
    rings[i] = rings[i] * sticker_val;
    rings_old[i] = rings[i]/mvar * sticker_val;
    p_rings_old[i] = p_rings[i] * sticker_val; // / mvar

    // dp_rings_old[i] = dp_rings[i] *  val *sticker_val; //  ==============================>>>>>>>>  ATTENTION
    dp_rings_old[i] = dp_rings[i] *  mvar *sticker_val; //  ==============================>>>>>>>>  ATTENTION
    sticker_rings[i] = sticker_val;
    //gradient also needs to be updated after this variable substitution with calculate_grad_error()
    rings_dummy[i] = rings_old[i] * val;

    drings[i] = dring_tmp/ val  ;  // dans le referentiel absolu ( comme si on l avait calcule')
  }
}
//--------------------------------------------------


struct ParamsForTomo {
  Gpu_Context *ctxstruct;
  float DETECTOR_DUTY_RATIO;
  int DETECTOR_DUTY_OVERSAMPLING;
} ;


class Patches_Geometry1D{
public:
  int dim;
  int wp;
  int wc;
  int np;
  int sc;
  int ll, lu;
  Patches_Geometry1D() {
  }
  Patches_Geometry1D(int dim, int wp, int wc) {
    this->dim=dim;
    this->wp=wp;
    this->wc=wc;
    sc = (wp-wc)/2;
    ll = wp-sc-wc;
    lu = iAlignUp(dim,wc)-sc;
    np = iAlignUp(dim,wc)/wc;
  }

  inline __host__ __device__  int local_p(int i)   const { return (i%wc)+sc;} ;
  inline __host__ __device__    int i_patch(int i) const { return i/wc; } ;
  inline  __host__ __device__ int noverlap(int lp) const {
    return 1 + lp/wc +(wp-1-lp)/wc;
  }
  inline  __host__ __device__ int lossoverlap(int gp) const {
    int res=0;
    if(gp<ll) {
      res=  (1+(ll-1-gp)/wc);
    }
    if(gp>=lu) {
      res +=  (1+(gp-lu)/wc);
    }
    return res;
  }
};

class Patches_Geometry2D{
public:
  Patches_Geometry1D   pgy;     //
  Patches_Geometry1D   pgx;     //
  int np;                       //
  int sizep2d;                  //
  int doppio;                   //

  Patches_Geometry2D() {
  }            ;

  Patches_Geometry2D(int dim0,int dim1, int dim_patches, int step_for_patches, int doppio) {
    pgy = Patches_Geometry1D( dim0, dim_patches, step_for_patches);
    pgx = Patches_Geometry1D( dim1, dim_patches, step_for_patches);
    np=pgy.np*pgx.np;
    sizep2d =   dim_patches*dim_patches ;
    this-> doppio = doppio;
  };
};



__global__  static  void put_patches_onimage_andsum_kernel( float *d_image  ,float *d_image_sum ,float * d_patches   ,Patches_Geometry2D  pg2d  ) {

  int gidx = threadIdx.x + blockIdx.x*blockDim.x;
  int gidy = threadIdx.y + blockIdx.y*blockDim.y;

  float sum[10];

  if( gidx<pg2d.pgx.dim   &&  gidy<pg2d.pgy.dim) {
    int ipx = pg2d.pgx.i_patch( gidx);
    int ipy = pg2d.pgy.i_patch( gidy) ;

    int ilx = pg2d.pgx.local_p( gidx);
    int ily = pg2d.pgy.local_p( gidy) ;

    int spx  =  max(   ipx - (pg2d.pgx.wp-1-ilx)/pg2d.pgx.wc ,  0             );
    int epx  =  min(   ipx + ilx / pg2d.pgx.wc +1            ,  pg2d.pgx.np   );

    int spy  =  max(   ipy - (pg2d.pgy.wp-1-ily)/pg2d.pgy.wc ,  0             );
    int epy  =  min(   ipy + ily / pg2d.pgy.wc +1            ,  pg2d.pgy.np   );


    // int noverlap=( pg2d.pgx.noverlap(ilx)-pg2d.pgx.lossoverlap(gidx))*(pg2d.pgy.noverlap(ily)-pg2d.pgy.lossoverlap(gidy));

    for(int ilayer=0; ilayer<pg2d.doppio; ilayer++) {
      sum[ilayer]=0.0f;
    }
    for(int Jpx = spx ;  Jpx < epx ; Jpx++ ) {
      for(int Jpy = spy   ;  Jpy < epy ; Jpy++) {

	int Jlx = ilx-(Jpx-ipx)*  pg2d.pgx.wc ;
	int Jly = ily-(Jpy-ipy)*  pg2d.pgy.wc ;

	int Jp =     pg2d.pgx.np*Jpy   + Jpx ;
	int Jl =     pg2d.pgx.wp*Jly   + Jlx ;


	for(int ilayer=0; ilayer<pg2d.doppio; ilayer++) {
	  sum[ilayer]+=  d_patches[ (Jp*pg2d.doppio + ilayer)*pg2d.sizep2d    +Jl    ] ;
	}
      }
    }

    int ip =     pg2d.pgx.np*ipy   + ipx ;
    int il =     pg2d.pgx.wp*ily   + ilx ;


    for(int ilayer=0; ilayer<pg2d.doppio; ilayer++) {
      d_image    [ (ilayer*pg2d.pgy.dim  +  gidy)*pg2d.pgx.dim  +   gidx  ]  = d_patches[ (ip*pg2d.doppio + ilayer)*pg2d.sizep2d    +il    ]  ;
      d_image_sum[ (ilayer*pg2d.pgy.dim  +  gidy)*pg2d.pgx.dim  +   gidx  ]  =  sum[ilayer]; // /noverlap ;
    }
  }
}


__global__  static  void rows_subtract_kernel( float *d_sino_error, float *d_rings,
					       int doppio,
					       int nprojs_span,
					       int num_bins
					       ) {

  int gidx = threadIdx.x + blockIdx.x*blockDim.x;
  int gidy = threadIdx.y + blockIdx.y*blockDim.y;

  if( gidx< num_bins  &&  gidy<doppio*nprojs_span) {
    int idoppio = gidy/nprojs_span;
    d_sino_error[ gidy*num_bins +gidx ] -=    d_rings[ gidx + idoppio*num_bins ] ;
  }
}

__global__  static  void rows_subtract_kernel_vectoriality( float *d_sino_error, float *d_rings,
                  int doppio,
                  int nprojs_span,
                  int num_bins,
                  float *angles_per_proj
                  ) {

  int gidx = threadIdx.x + blockIdx.x*blockDim.x;
  int gidy = threadIdx.y + blockIdx.y*blockDim.y;

  if( gidx< num_bins  &&  gidy<doppio*nprojs_span) {
    int idoppio = gidy / nprojs_span;
    int iangle  = gidy % nprojs_span ;
    if(idoppio*2<doppio) {
      d_sino_error[ gidy*num_bins +gidx ] -=    d_rings[ gidx + (idoppio/2)*num_bins ]*(-cos(angles_per_proj[iangle])) ; // idoppio is not used : the same rings are substracted to both components, since they come from the same signal (for Differential PCT)
    } else {
      d_sino_error[ gidy*num_bins +gidx ] -=    d_rings[ gidx + (idoppio/2)*num_bins ]*(+sin(angles_per_proj[iangle]))  ;
    }
  }
}

__global__  static  void rows_subtract_kernel_mw( float *d_sino_error, float *d_rings,
						  int doppio,
						  int nprojs_span,
						  int num_bins,
						  int  nwavelets,
						  int w_step,
						  float *d_interp_wavelet
						  ) {
  int gidx = threadIdx.x + blockIdx.x*blockDim.x;
  int gidy = threadIdx.y + blockIdx.y*blockDim.y;

  if( gidx< num_bins  &&  gidy<doppio*nprojs_span) {
    int idoppio = gidy/nprojs_span;
    for(int iw=0; iw<nwavelets ; iw++) {
      int idist = abs(gidy-iw*w_step );
      if(idist<3*w_step)  d_sino_error[ gidy*num_bins +gidx ] -=     d_rings[ gidx + (idoppio+iw*doppio)*num_bins ]*d_interp_wavelet[idist];
    }
  }
}


__global__  static  void rows_subtract_kernel_mw_vectoriality( float *d_sino_error, float *d_rings,
              int doppio,
              int nprojs_span,
              int num_bins,
              int  nwavelets,
              int w_step,
              float *d_interp_wavelet,
              float *angles_per_proj)
{
  int gidx = threadIdx.x + blockIdx.x*blockDim.x;
  int gidy = threadIdx.y + blockIdx.y*blockDim.y;

  if( gidx< num_bins  &&  gidy<doppio*nprojs_span) {
    int idoppio = gidy / nprojs_span;
    int iangle  = gidy % nprojs_span;

    float mult = 1.0f;
    if (idoppio*2<doppio) mult = -cos(angles_per_proj[iangle]);
    else mult = sin(angles_per_proj[iangle]);

    for(int iw=0; iw<nwavelets ; iw++) {
      int idist = abs(iangle-iw*w_step );
      if(idist<3*w_step)  d_sino_error[ gidy*num_bins +gidx ] -=     d_rings[gidx + (idoppio+iw*doppio)*num_bins  ] * d_interp_wavelet[idist] * mult; // <---
    }
  }
}




__global__  static  void rows_reduce_kernel_mw( float *d_sino_error, float *d_rings,
						int doppio,
						int nprojs_span,
						int num_bins,
						int  nwavelets,
						int w_step,
						float *d_interp_wavelet
						) {

  int gidx = threadIdx.x + blockIdx.x*blockDim.x;
  int gidy = threadIdx.y + blockIdx.y*blockDim.y;
  float sums[8] = {0,0,0,0, 0,0,0,0};
  if( gidx< num_bins  &&  gidy<doppio) {
    for(int ipro=0; ipro<nprojs_span; ipro++) {
      for(int iw=0; iw<nwavelets ; iw++) {
        int idist = abs(ipro-iw*w_step);
        if(idist<3*w_step) sums[iw]  +=  d_sino_error[  (gidy*nprojs_span+ipro)*num_bins   +  gidx ]  * d_interp_wavelet[idist] ;
      }
    }
    //assign the sums to the "nwavelets" rings
    for (int i=0; i < nwavelets; i++) d_rings[ gidx + (gidy+i*doppio)*num_bins ] +=  sums[i]  ;
  }
}

__global__  static  void rows_reduce_kernel_mw_vectoriality( float *d_sino_error, float *d_rings,
             int doppio,
             int nprojs_span,
             int num_bins,
             int  nwavelets,
             int w_step,
             float *d_interp_wavelet,
             float *angles_per_proj
             ) {

  int gidx = threadIdx.x + blockIdx.x*blockDim.x;
  int gidy = threadIdx.y + blockIdx.y*blockDim.y;
  float sums[8] = {0,0,0,0, 0,0,0,0};
  float mult = 1.0f;
  if( gidx< num_bins  &&  gidy<doppio) {
    for(int ipro=0; ipro<nprojs_span; ipro++) {
      if(gidy*2 < doppio) mult = (-cos(angles_per_proj[ipro]));
      else mult = (+sin(angles_per_proj[ipro]));
      for(int iw=0; iw<nwavelets ; iw++) {
        int idist = abs(ipro-iw*w_step);
        if(idist<3*w_step) sums[iw]  +=  d_sino_error[  (gidy*nprojs_span+ipro)*num_bins   +  gidx ] * d_interp_wavelet[idist] * mult;
      }
    }
    //assign the sums to the "nwavelets" rings
    for (int i=0; i < nwavelets; i++) d_rings[ gidx + (gidy+i*doppio)*num_bins ] +=  sums[i]  ;
  }
}


__global__  static  void rows_reduce_kernel_vectoriality( float *d_sino_error, float *d_rings,
							  int doppio,
							  int nprojs_span,
							  int num_bins,
							  float *angles_per_proj
							  ) {

  int gidx = threadIdx.x + blockIdx.x*blockDim.x;
  int gidy = threadIdx.y + blockIdx.y*blockDim.y;
  float sum=0.0f;

  if( gidx< num_bins  &&  gidy<doppio) {
    int idoppio = gidy ;
    for(int ipro=0; ipro<nprojs_span; ipro++) {
      int iangle =  ipro ;
      if(idoppio*2<doppio) {
	sum  +=  d_sino_error[  (gidy*nprojs_span+ipro)*num_bins   +  gidx ] *(-cos(angles_per_proj[iangle]))  ;
      } else {
	sum  +=  d_sino_error[  (gidy*nprojs_span+ipro)*num_bins   +  gidx ] *(+sin(angles_per_proj[iangle]))  ;
      }
    }
    if(idoppio*2<doppio) {
      d_rings[ gidx + gidy*num_bins ] +=  sum ;
    } else {
      d_rings[ gidx + (gidy%(doppio/2))*num_bins ] +=  sum ;
    }
  }
}


__global__  static  void rows_reduce_kernel( float *d_sino_error, float *d_rings,
					     int doppio,
					     int nprojs_span,
					     int num_bins
					     ) {

  int gidx = threadIdx.x + blockIdx.x*blockDim.x;
  int gidy = threadIdx.y + blockIdx.y*blockDim.y;
  float sum=0.0f;

  if( gidx< num_bins  &&  gidy<doppio) {
    for(int ipro=0; ipro<nprojs_span; ipro++) {
      sum  +=  d_sino_error[  (gidy*nprojs_span+ipro)*num_bins   +  gidx ]   ;
    }
    d_rings[ gidx + gidy*num_bins ] +=  sum ;
  }
}


//----------- PP.add : ramp kernel for precondition ------------------
// __global__ static void  ramp_filter_kernel(
// 					   cufftComplex* d_i_data,
// 					   int sizeX,
// 					   int sizeY,
// 					   float vmax,
// 					   float offset)
// {
//   int gidx = threadIdx.x + blockIdx.x*blockDim.x;
//   int gidy = threadIdx.y + blockIdx.y*blockDim.y;
//   if (gidx < sizeX && gidy < sizeY) {
//     float ramp ;
//     ramp = vmax/(sizeX)/sizeX*(gidx+1);
//     d_i_data[gidy*sizeX+gidx].x = d_i_data[gidy*sizeX+gidx].x * ramp;
//     d_i_data[gidy*sizeX+gidx].y = d_i_data[gidy*sizeX+gidx].y * ramp;
//   }
// }
// //----------- End of PP.add ------------------






__global__  static  void prepare_gradient_onpatches_kernel( float *d_image, float *d_image_sum, float *d_error,
							    float * d_patches, float * d_patches_gradient,
							    Patches_Geometry2D  pg2d , int todo, int done,
							    float peso_overlap) {

  int gidx = threadIdx.x + blockIdx.x*blockDim.x   +done;
  int sizep =   pg2d.doppio * pg2d.sizep2d;

  // if( gidx < pg2d.np * sizep ) {
  if( gidx < done+todo ) {
    int ip = gidx/sizep ;

    int ipx = ip %   pg2d.pgx.np ;
    int ipy = ip /   pg2d.pgx.np ;

    int il = gidx%sizep ;

    int ilx =  il                %  pg2d.pgx.wp ;
    int ily = (il/pg2d.pgx.wp)   %  pg2d.pgy.wp ;
    int ilz =  il/pg2d.sizep2d      ;  // (pg2d.pgx.wp*pg2d.pgy.wp)  ;


    int xx =  + ipx*pg2d.pgx.wc -pg2d.pgx.sc  +  ilx    ;
    int yy =  + ipy*pg2d.pgy.wc -pg2d.pgy.sc + ily ;

    float res4grad=0.0f;
    float res=0.0f;

    if( xx>=0 &&  yy>=0 && yy<pg2d.pgy.dim && xx<pg2d.pgx.dim) {
      int pos_image =(ilz*pg2d.pgy.dim + yy )*pg2d.pgx.dim  + xx ;

      float tmp;

      if( ilx>=pg2d.pgx.sc && ilx<(pg2d.pgx.sc+pg2d.pgx.wc) && ily>=pg2d.pgy.sc && ily<(pg2d.pgy.sc+pg2d.pgy.wc) ) {
	int nov = ( pg2d.pgx.noverlap(ilx)-pg2d.pgx.lossoverlap(xx))*(pg2d.pgy.noverlap(ily)-pg2d.pgy.lossoverlap(yy));;
	tmp = d_error[ pos_image  ];

	res4grad = tmp+(  d_image_sum[pos_image] - d_image[pos_image]*nov )*peso_overlap ;
	tmp =  0.0f ;
      } else{
	tmp = (  d_image[pos_image] - d_patches[ gidx  ]  );
	res4grad = tmp * peso_overlap;
      }
      res  =  tmp ;
    }
    d_patches_gradient[ gidx  ]  = res4grad;
    d_patches[ gidx  ]           =  res ;
  }
}



__global__  static  void kern_shrink_fista_compact_l1( float *d_w,  float *d_dw, float *d_wold,  float  weight, float Lip,
						       float *d_notzero, float told,  float tnew , int todo, int done, int iter){
  int gid;
  float w0,w1,tmp,  notzero, wold;

  gid = threadIdx.x + blockIdx.x*blockDim.x+done;

  if( gid < done+todo  ) {

    w0 = d_w[gid];
    tmp = w0+d_dw[gid]/Lip;

    w1 = copysignf( max(fabs(tmp)-weight,0.0f) , tmp) ;

    wold = d_wold[gid];
    d_wold[gid]=w1 ;
    w1=w1 +  ((told-1)/tnew) /*(iter-1.0)/(iter+2.1)*/ * (w1-wold) ;
    d_w [gid] = w1;
    notzero=0.0f;
    if(w1!=0.0f) {
      notzero = 1.0f;
    }
    d_notzero[gid]=notzero;
  }
}

__global__  static  void kern_shrink_fista_compact_smooth( float *d_w,  float *d_dw, float *d_wold,  float  weight, float Lip,
							   float *d_notzero, float told,  float tnew , int todo, int done, float s){
  int gid;
  float w0,w1,tmp,  notzero, wold;
  gid = threadIdx.x + blockIdx.x*blockDim.x+done;
  if( gid < done+todo  ) {
    w0 = d_w[gid];
    tmp = w0+d_dw[gid]/Lip;
    w1 = copysignf(0.5*(fabs(tmp)-weight-s+sqrt( (fabs(tmp)-weight-s)*(fabs(tmp)-weight-s) + 4*s*fabs(tmp))),tmp);

    wold = d_wold[gid];
    d_wold[gid]=w1 ;
    w1=w1 +  ((told-1)/tnew) * (w1-wold) ;
    d_w [gid] = w1;
    notzero=0.0f;
    if(w1!=0.0f) {
      notzero = 1.0f;
    }
    d_notzero[gid]=notzero;
  }
}



//============

void     prepare_kernels12(float *kernel1, float *kernel2, int rkern , float sigma  )
{
  int i,j;
  float x;
  float sum=0;
  for(i=0; i< 2*rkern+1  ; i++) {
    x = (rkern-i)/sigma;
    kernel1[i]=exp(-x*x/2.0);
    sum+=kernel1[i] ;
  }
  for(i=0; i<2*rkern+1; i++) {
    kernel1[i]/=sum;
  }
  for(i=0; i<4*rkern+1; i++) {
    kernel2[i]=0.0;
  }
  kernel1[rkern]-=1.0;
  for(i=0; i<2*rkern+1; i++) {
    for(j=0; j<2*rkern+1; j++) {
      kernel2[i+j] += kernel1[i]*kernel1[j];
    }
  }
}


void convolve_unsharp( float *ringstmp, float *h_rings, float *kernel2,
		       int rkern, int N, int num_bins) {
  int id,i,j;
  // for(i=0; i<N* num_bins; i++) ringstmp[i]= h_rings[i];
  // return;
  for(i=0; i<N* num_bins; i++) ringstmp[i]=0;

  // float sum=0;
  // for(j=-2*rkern; j<=2*rkern ; j++   ) sum += kernel2[2*rkern + j]  ;
  // printf(" %d  %d %e \n", N, rkern , sum);

  for(id=0; id<N; id++) {
    for(i=0; i< num_bins; i++){
      // for(j=max(0,(i-2*rkern)); j<=min((num_bins-1), (i+2*rkern)) ; j++   ) {
      for(j=(i-2*rkern); j<=(i+2*rkern) ; j++   ) {
	if (j<0) {
	  ringstmp[id*num_bins+i] +=   kernel2[2*rkern + i-j] * h_rings[id*num_bins + 0]  ;
	} else if(j> (num_bins-1)){
	  ringstmp[id*num_bins+i] +=   kernel2[2*rkern + i-j] * h_rings[id*num_bins + (num_bins-1)]  ;
	} else{
	  ringstmp[id*num_bins+i] +=   kernel2[2*rkern + i-j] * h_rings[id*num_bins + j]  ;
	}
      }
    }
  }
}

//static int counterDfp = 0;


class Buffers {
public:
  float *d_w;                   //
  float *d_dw;                  //
  float *d_df;                  //
  float * d_p;
  float * d_dp;
  int   *d_invertiti;
  float *d_dfprojected;


  float *d_wold;

  float *d_notzero;
  float *d_forreduce;
  double *DDd_forreduce;
  float *d_forreduce1;
  float *d_ones;
  double *DDd_ones;

  float *d_image;
  float *d_image_sum;
  float *d_image_error;
  float *d_sino_error;

  float *d_sino_data;
  float *d_sino;

  float *d_patches;
  float *d_patches_gradient;
  float *d_X;
  float * d_angles_per_proj;

  int ncomps;
  int sizepatch;
  Patches_Geometry2D pg2d;
  int terminenoto;
  int imsize ;


  int rkern;
  float *kernel1;
  float *kernel2;
  float *ringstmp ;
  float *rings    ;
  float *drings   ;
  float *rings_old;
  float *rings_tmp;

  //for multirings
  float* d_interp_wavelet;
  float* alpharings_multi;
  int multirings_size;
  int w_step;

  float *D_rings;

  int num_bins;
  int nprojs_span;

  int optim_algorithm;


  int iterringheight;
  int iterringsize;

  void set_w2zero(float *A, float *r=NULL) {
    CUDA_SAFE_CALL( cudaMemset( A ,0,    pg2d.np*ncomps*sizeof(float)     ));
    if(r) {
      memset( r, 0, multirings_size *sizeof(float) );
    }

  }
  Buffers() {
  };

  Buffers(  Patches_Geometry2D & pg2d , int ncomps, float* patches, float *sino_data,
	    ParamsForTomo &p4t, int iterringheightA,  int iterringsizeA,
	    int optim_algorithm) {

    this->iterringheight = iterringheightA;
    this->iterringsize = iterringsizeA;
    this->
      optim_algorithm = optim_algorithm;
    this->terminenoto=1;
    this->imsize =   pg2d.pgy.dim*pg2d.pgx.dim* pg2d.doppio;

    this->pg2d=pg2d;
    this->ncomps=ncomps;
    this->num_bins =  p4t.ctxstruct->num_bins;
    this->nprojs_span = p4t.ctxstruct->nprojs_span;


    this->sizepatch =  pg2d.doppio* pg2d.sizep2d  ;



    CUDA_SAFE_CALL( cudaMalloc( &(this->d_angles_per_proj), nprojs_span*sizeof(float)));
    CUDA_SAFE_CALL( cudaMemcpy( this->d_angles_per_proj ,p4t.ctxstruct->angles_per_proj ,nprojs_span*sizeof(float) , cudaMemcpyHostToDevice));

    this->d_interp_wavelet =NULL;
    this->multirings_size = pg2d.doppio*num_bins*p4t.ctxstruct->NUMBER_OF_RINGS;

    if(optim_algorithm == 1 ) {
      CUDA_SAFE_CALL( cudaMalloc( &(d_w), pg2d.np*ncomps*sizeof(float)));
      CUDA_SAFE_CALL( cudaMalloc( &(d_wold), pg2d.np*ncomps*sizeof(float)));
      CUDA_SAFE_CALL( cudaMalloc( &(d_dw), pg2d.np*ncomps*sizeof(float)));
      CUDA_SAFE_CALL( cudaMalloc( &(d_notzero), (pg2d.np*ncomps)*sizeof(float)));


    } else if(optim_algorithm == 2 ){
      CUDA_SAFE_CALL( cudaMalloc( &(d_w), pg2d.np*ncomps*sizeof(float)));
      CUDA_SAFE_CALL( cudaMalloc( &(d_dw), pg2d.np*ncomps*sizeof(float)));
      CUDA_SAFE_CALL( cudaMalloc( &(d_p), pg2d.np*ncomps*sizeof(float)));
      CUDA_SAFE_CALL( cudaMalloc( &(d_dp), pg2d.np*ncomps*sizeof(float)));
      CUDA_SAFE_CALL( cudaMalloc( &(d_df), pg2d.np*ncomps*sizeof(float)));

      int Nred = 1+( pg2d.np*ncomps)/WKSIZE;

      CUDA_SAFE_CALL( cudaMalloc( &(d_forreduce ),(Nred)*sizeof(float)));
      CUDA_SAFE_CALL( cudaMalloc( &(d_forreduce1),(Nred)*sizeof(float)));
      CUDA_SAFE_CALL( cudaMalloc( &(d_ones ),(Nred)*sizeof(float)));
      float *ones = new float [Nred];
      for(int i=0; i< Nred; i++) {
	ones[i]=1.0;
      }
      delete ones;
      CUDA_SAFE_CALL( cudaMemcpy( d_ones , ones, Nred*sizeof(float), cudaMemcpyHostToDevice));
    } else if(optim_algorithm == 4 ){
      CUDA_SAFE_CALL( cudaMalloc( &(d_w), pg2d.np*ncomps*sizeof(float)));
      CUDA_SAFE_CALL( cudaMalloc( &(d_dw), pg2d.np*ncomps*sizeof(float)));
      CUDA_SAFE_CALL( cudaMalloc( &(d_p), pg2d.np*ncomps*sizeof(float)));
      CUDA_SAFE_CALL( cudaMalloc( &(d_dp), pg2d.np*ncomps*sizeof(float)));
      CUDA_SAFE_CALL( cudaMalloc( &(d_invertiti), pg2d.np*ncomps*sizeof(int)));
      CUDA_SAFE_CALL( cudaMalloc( &(d_dfprojected), pg2d.np*ncomps*sizeof(float)));
      CUDA_SAFE_CALL( cudaMalloc( &(d_df), pg2d.np*ncomps*sizeof(float)));

      int Nred = 1+( pg2d.np*ncomps)/WKSIZE;

      CUDA_SAFE_CALL( cudaMalloc( &(d_forreduce ),(Nred)*sizeof(float)));
      CUDA_SAFE_CALL( cudaMalloc( &(d_forreduce1),(Nred)*sizeof(float)));
      CUDA_SAFE_CALL( cudaMalloc( &(d_ones ),(Nred)*sizeof(float)));
      float *ones = new float [Nred];
      for(int i=0; i< Nred; i++) {
	ones[i]=1.0;
      }
      CUDA_SAFE_CALL( cudaMemcpy( d_ones , ones, Nred*sizeof(float), cudaMemcpyHostToDevice));
      delete ones;
    }
    else if(optim_algorithm == 5 ){
      int Nred = 1+( pg2d.np*ncomps)/WKSIZE;

      CUDA_SAFE_CALL( cudaMalloc( &(d_w), pg2d.np*ncomps*sizeof(float)));
      CUDA_SAFE_CALL( cudaMalloc( &(d_wold), pg2d.np*ncomps*sizeof(float)));
      CUDA_SAFE_CALL( cudaMalloc( &(d_dw), pg2d.np*ncomps*sizeof(float)));
      CUDA_SAFE_CALL( cudaMalloc( &(d_p), pg2d.np*ncomps*sizeof(float)));
      CUDA_SAFE_CALL( cudaMalloc( &(d_dp), pg2d.np*ncomps*sizeof(float)));
      CUDA_SAFE_CALL( cudaMalloc( &(d_df), pg2d.np*ncomps*sizeof(float)));
      CUDACHECK;
    
      CUDA_SAFE_CALL( cudaMalloc( &(DDd_ones ),(Nred)*sizeof(double)));
      double *DDones = new double [Nred];
      for(int i=0; i< Nred; i++) {
	DDones[i]=1.0;
      }
      CUDA_SAFE_CALL( cudaMemcpy( DDd_ones , DDones, Nred*sizeof(double), cudaMemcpyHostToDevice));
      CUDACHECK;



      //for line search
      CUDA_SAFE_CALL( cudaMalloc( &(d_notzero), (pg2d.np*ncomps)*sizeof(float))); //should not be necessary
      //int Nred = 1+( pg2d.np*ncomps)/WKSIZE;
      CUDA_SAFE_CALL( cudaMalloc( &(d_forreduce ),(Nred)*sizeof(float)));
      CUDA_SAFE_CALL( cudaMalloc( &(DDd_forreduce ),(Nred)*sizeof(double)));

      CUDA_SAFE_CALL( cudaMalloc( &(d_ones ),(Nred)*sizeof(float)));
      CUDACHECK;

      float *ones;
      ones = new float[Nred];
      for(int i=0; i< Nred; i++) {
	ones[i]=1.0;
      }
      CUDA_SAFE_CALL( cudaMemcpy( d_ones , ones, Nred*sizeof(float), cudaMemcpyHostToDevice));

      CUDACHECK;
      delete ones;
      delete DDones;
    }


    if (p4t.ctxstruct->NUMBER_OF_RINGS > 1) {
      int nrings = p4t.ctxstruct->NUMBER_OF_RINGS;
      int nref = ilog2(nextpow2(nprojs_span/(nrings-1)));
      w_step = ipow(2,nref);
      float* interp_wavelet;
      interp_wavelet = compute_wavelet(7, nref, nrings); //size: 3*(2**nref) for 4 rings
      CUDA_SAFE_CALL(cudaMalloc(&d_interp_wavelet, (nrings-1)*w_step*sizeof(float)));
      CUDA_SAFE_CALL(cudaMemcpy(d_interp_wavelet, interp_wavelet, (nrings-1)*w_step*sizeof(float), cudaMemcpyHostToDevice));
      this->alpharings_multi = (float*) malloc(nrings*sizeof(float) ) ;
      {
	for(int i=0; i< nrings; i++) {
	  float sum=0.0,d;
	  float X0 = w_step*i;
	  for(int k=-3*w_step+1; k<3*w_step; k++) {
	    d = interp_wavelet[abs(k)];
	    if ( ( X0+d >=0 ) && (X0+d<nprojs_span) ) {
	      sum+=d*d;
	    }
	  }
	  this->alpharings_multi[i] = sum/this->num_bins ;
	  if(this->alpharings_multi[i]==0) this->alpharings_multi[i]=1;
	  // this->alpharings_multi[i] = 1 ; 
	}
      }
      free(interp_wavelet) ;
    }

    // =========================
    // === dans le cas de slice multiple ces allocation peuvent etre reduites
    // === en mettant la boucles sur les slices a l' exterieure. Les suivantes suffiraient alors
    // === de taille adaptee pour une slice seulement.
    CUDA_SAFE_CALL( cudaMalloc( &d_image      ,  pg2d.pgy.dim * pg2d.pgx.dim *  pg2d.doppio  * sizeof(float)    ) ) ;
    CUDA_SAFE_CALL( cudaMalloc( &d_image_sum  ,  pg2d.pgy.dim * pg2d.pgx.dim *  pg2d.doppio  * sizeof(float)    ) ) ;
    CUDA_SAFE_CALL( cudaMalloc( &d_image_error,  pg2d.pgy.dim * pg2d.pgx.dim *  pg2d.doppio  * sizeof(float)    ) ) ;


    CUDA_SAFE_CALL( cudaMalloc( &d_sino      , num_bins*nprojs_span*  pg2d.doppio  * sizeof(float)    ) )    ;
    CUDA_SAFE_CALL( cudaMalloc( &d_sino_error, num_bins*nprojs_span*  pg2d.doppio  * sizeof(float)    ) )    ;

    CUDA_SAFE_CALL( cudaMalloc( &d_patches    , pg2d.np * sizepatch  * sizeof(float)    ) ) ;
    CUDA_SAFE_CALL( cudaMalloc( &d_patches_gradient , pg2d.np * sizepatch  * sizeof(float)    ) )   ;
    //
    // =======================================================


    CUDA_SAFE_CALL( cudaMalloc( &d_sino_data , num_bins*nprojs_span*  pg2d.doppio  * sizeof(float)    ) )    ;
    CUDA_SAFE_CALL( cudaMalloc( &d_X, ncomps*sizepatch*sizeof(float) ));
    CUDA_SAFE_CALL( cudaMemcpy( d_X , patches, ncomps*  sizepatch   *sizeof(float) , cudaMemcpyHostToDevice));
    CUDA_SAFE_CALL( cudaMemcpy( d_sino_data     , sino_data   ,   num_bins*nprojs_span *  pg2d.doppio  * sizeof(float)  , cudaMemcpyHostToDevice));

    //---------------


    rkern=0;
    if(this->iterringheight) {

      if(this->iterringsize) {
	rkern = (int) (  3*iterringsize   +1);
	assert(rkern<1000);
	kernel1= new float[2*rkern+1];
	kernel2= new float[4*rkern+1];
	prepare_kernels12(kernel1, kernel2, rkern ,  iterringsize );
      } else {
	kernel1 = NULL;
	kernel2 = NULL;
      }
      ringstmp  =  new float  [multirings_size];
      rings     =  new float  [multirings_size];
      drings    =  new float  [multirings_size];
      rings_old =  new float  [multirings_size];
      rings_tmp =  new float  [multirings_size];

      CUDA_SAFE_CALL( cudaMalloc( &D_rings , multirings_size  * sizeof(float)    ) )    ;

      memset( rings      , 0,  multirings_size*sizeof(float) ) ;
      memset( drings     , 0,  multirings_size*sizeof(float) ) ;
      memset( rings_old  , 0,  multirings_size*sizeof(float) ) ;

    } else {
      kernel1=NULL;
      kernel2   = NULL ;
      ringstmp  = NULL ;
      rings     = NULL ;
      drings    = NULL ;
      rings_old = NULL ;
      rings_tmp = NULL ;
      D_rings   = NULL;
    }


  }

  ~Buffers(){

    if(this->optim_algorithm == 1 ) {
      CUDA_SAFE_CALL( cudaFree( d_w));
      CUDA_SAFE_CALL( cudaFree( (d_wold)));
      CUDA_SAFE_CALL( cudaFree( (d_dw)));
      CUDA_SAFE_CALL( cudaFree( (d_notzero)));
    } else if(this->optim_algorithm == 2 ){
      CUDA_SAFE_CALL( cudaFree( d_w));
      CUDA_SAFE_CALL( cudaFree( (d_dw)));
      CUDA_SAFE_CALL( cudaFree( d_p));
      CUDA_SAFE_CALL( cudaFree( d_dp));
      CUDA_SAFE_CALL( cudaFree( d_df));
    } else if(this->optim_algorithm == 4 ){
      CUDA_SAFE_CALL( cudaFree( d_w));
      CUDA_SAFE_CALL( cudaFree( (d_dw)));
      CUDA_SAFE_CALL( cudaFree( d_p));
      CUDA_SAFE_CALL( cudaFree( d_dp));
      CUDA_SAFE_CALL( cudaFree( d_invertiti));
      CUDA_SAFE_CALL( cudaFree( d_dfprojected));
      CUDA_SAFE_CALL( cudaFree( d_df));
    }
    else if (this->optim_algorithm == 5) {
      CUDA_SAFE_CALL( cudaFree( d_w));
      CUDA_SAFE_CALL( cudaFree( d_dw));
      CUDA_SAFE_CALL( cudaFree( d_wold));
      CUDA_SAFE_CALL( cudaFree( d_p));
      CUDA_SAFE_CALL( cudaFree( d_dp));
      CUDA_SAFE_CALL( cudaFree( d_df));

      CUDA_SAFE_CALL( cudaFree(d_notzero ));
      CUDA_SAFE_CALL( cudaFree(d_forreduce ));
      CUDA_SAFE_CALL( cudaFree(d_ones ));
    }
    CUDA_SAFE_CALL( cudaFree(   d_angles_per_proj     ))    ;

    if(d_interp_wavelet) {
      CUDA_SAFE_CALL( cudaFree(    d_interp_wavelet    ))   ;
      free(alpharings_multi);
    }


    CUDA_SAFE_CALL( cudaFree( d_image       ))  ;
    CUDA_SAFE_CALL( cudaFree( d_image_sum   ))  ;
    CUDA_SAFE_CALL( cudaFree( d_image_error ))  ;


    CUDA_SAFE_CALL( cudaFree( d_sino_data  ) )  ;
    CUDA_SAFE_CALL( cudaFree( d_sino       ) )  ;
    CUDA_SAFE_CALL( cudaFree( d_sino_error ) )  ;

    CUDA_SAFE_CALL( cudaFree( d_patches          ));
    CUDA_SAFE_CALL( cudaFree( d_patches_gradient ));
    CUDA_SAFE_CALL( cudaFree( d_X                ));

    if( this->iterringheight){
      delete   ringstmp  ;
      delete   rings     ;
      delete   drings    ;
      delete   rings_old ;
      delete   rings_tmp ;
      if( this->iterringsize){
	delete kernel1;
	delete kernel2;
      }

      CUDA_SAFE_CALL( cudaFree( D_rings                ));
    }
  }



  float rinormalizza_grad(float *A, float *hA=NULL) {
    float norma =  cublasSnrm2( pg2d.np*ncomps  ,  A   , 1);
    if( hA) {
      float norma2=0;
      {
	for(int i=0; i<   multirings_size ; i++) {
	  norma2+=hA[i]*hA[i];
	}
	norma = sqrt(norma*norma+norma2);
      }
      for(int i=0; i<  multirings_size    ; i++)  hA[i]/=norma;
    }
    cublasSscal(  pg2d.np*ncomps , 1.0f/norma ,A  , 1) ;
    return norma;
  }


  void complete_gradient_l1(  float *coeffs  , float * grad_tot , float *grad , float weight , float* rings = NULL, float* drings = NULL, float* minus_grad_tot_rings = NULL) {

    dim3 blk,grd;
    int totpatch = pg2d.np*ncomps;


    cudaMemset(grad_tot,0,totpatch*sizeof(float));


    blk = dim3( WKSIZE , 1 , 1 );
    int done=0;

    while(done<totpatch) {
      //          if(done) printf("WARNING by pieces line %d \n", __LINE__);

      int nb =   min(  iDivUp(totpatch-done, WKSIZE), 0xFFFF )  ;
      int todo = min( nb* WKSIZE ,totpatch -done   ) ;
      grd = dim3( nb , 1, 1 );
      kern_complete_grad_l1<<<grd,blk>>>( todo, done, coeffs , grad_tot , grad ,    weight);
      done+=todo;
    }
    CUDACHECK;

    if (rings) {
      float isign = 0.0f;
      for (int i=0; i < multirings_size ; i++) {
        if (rings[i] > 0.0f) isign = 1.0f;
        if (rings[i] < 0.0f) isign = -1.0f;
        if (rings[i] == 0 && drings[i] != 0) isign = copysignf(1.0f, drings[i]);
	//        if (rings[i] == 0) isign = 0;
        minus_grad_tot_rings[i] = drings[i] - isign*weight;
      }
    }
    //printf("grad_tot : %e\n",cublasSasum(totpatch,grad_tot,1));

  }

  float diffpenl1rings(float* h_rings, float* h_rings_p, float alpha, float weight_rings, float* h_rings_mult) {
    float acc = 0, t, isign = 0;
    for (int i=0; i<  multirings_size    ; i++) {
      t = h_rings[i]+alpha*h_rings_p[i];
      if (t > 0.0f) isign = -weight_rings;
      if (t < 0.0f) isign =  weight_rings;
      acc += isign *h_rings_p[i]*h_rings_mult[i] ;
      acc += -  ( (t==0.0f) )*weight_rings *h_rings_p[i]*h_rings_mult[i]  ;
    }
    return acc;
  }

  void  line_search_SG( float velocita_iniziale, float  velocita_iniziale_q,
			float * d_p,
			float * d_w,
			float * d_notzero,
			float * d_dw,
			float init_value,
			float weight,
			int ncomps,
			double k,
			FILE* log,
			float *a,
			float* mult,
			float* h_rings = NULL,
			float* h_rings_p = NULL,
			float* h_rings_mult = NULL,
			float weight_rings = 0)
  {
    
    //float step = 0.00001;
    double step = init_value;
    double alpha = 0.0;
    double dh_quad_0 , dh_0 , dh_quad , dh ,     al , ah;
    // float  daold ;
    
    dh_quad_0 = 0.0;
    dh_0      = 0.0;
    dh_quad_0 = 0.0;
    dh        = 0.0;
    
    /// Given d_w and p , calculates dh = h'(0) = p^t*grad(J(x))
    /// dh_quad = the term coming from the quadratic part of the objective
    /// dh = dh_quad + the term coming from the penalty
    
    dh_quad_0 = -velocita_iniziale_q;
    //    {
    
    //      step=0.0;      float delta = +call_kern_diffpenl1mult(ncomps , d_w , d_p , step , weight, mult);
    //      printf("  CONFRONTO %e %e %e \n", velocita_iniziale-velocita_iniziale_q, delta);
    //      printf(" CONFRONTO %e %e %e \n",-velocita_iniziale  ,  dh_quad_0-delta, -velocita_iniziale- ( dh_quad_0-delta)   );
    //    }
    step = 1.2* velocita_iniziale/k;
    
    
    dh_0 = -velocita_iniziale;
    
    // float aggiunta = call_kern_diff_pen_l1( ncomps,  d_w,  d_p,  1.0,  weight  );
    // printf(" AGGIUNTA %e dh_quad_0 %e  %e %e \n",  aggiunta,   ( dh_quad_0  )  , dh_quad_0 ,dh_0 );
    // step+=aggiunta/k;
    dh = dh_quad_0 + k*step;
    {
      double delta ;
#ifdef USEDD
      delta = DDcall_kern_diffpenl1mult(ncomps , d_w , d_p , step , weight, mult);
#else
      delta = call_kern_diffpenl1mult(ncomps , d_w , d_p , step , weight, mult);
#endif
      if (h_rings) delta += diffpenl1rings(h_rings, h_rings_p, step, weight_rings, h_rings_mult);
      dh -= delta;
    }
    {
      al=0.0;
      ah=step;
      double fp_l= dh_0;
      double fp_h= dh;
      // alpha = 0.5*(al+ah);
      double alphaold=-1;
      int iter;
      for( iter=0; iter<10; iter++) {
	alpha = (al*fp_h- ah*fp_l)/(fp_h- fp_l );
	if( fabs( (ah-al)/( fabs(ah)+fabs(al)))<1.5e-7 ) break;
	if( fabs( (alpha-alphaold)/( alpha+alphaold))<1.5e-7 ) break;
	dh_quad = dh_quad_0 + alpha*k;
	dh = dh_quad;
	
	double delta ;
#ifdef USEDD
	delta = DDcall_kern_diffpenl1mult(ncomps , d_w , d_p , alpha , weight, mult);
#else
	delta = call_kern_diffpenl1mult(ncomps , d_w , d_p , alpha , weight, mult);
#endif
	if (h_rings) delta += diffpenl1rings(h_rings, h_rings_p, step, weight_rings, h_rings_mult);
	dh -= delta;
	
	//printf("  fp_l %e , fp_hl %e , dh  l %e  delta %e \n",   fp_l, fp_h, dh, delta );
	if(  fabs(dh)>min(fabs(fp_l),fabs(fp_h))/2.0f) { //TODO: see if bracketing is always necessary
	  //printf(" DEVIAZIONE \n");
	  double alphadev = (al+ah)/2.0f;
	  double dh_quad_dev = dh_quad_0 + alphadev*k;
	  double dh_dev = dh_quad_dev;
	  {
	    double delta;
	    
#ifdef USEDD
	    delta = DDcall_kern_diffpenl1mult(ncomps , d_w , d_p , alphadev , weight,mult);
#else
	    delta = call_kern_diffpenl1mult(ncomps , d_w , d_p , alphadev , weight,mult);
#endif
	    if (h_rings) delta += diffpenl1rings(h_rings, h_rings_p, step, weight_rings, h_rings_mult);
	    dh_dev -= delta;
	  }
	  if( fabs(dh)>fabs(dh_dev) ) {
	    alpha = alphadev;
	    dh = dh_dev;
	  } else {
	    //printf(" NO \n");
	  }
	}
	alphaold=alpha;
	// printf(" dh %e\n", dh);
	if (dh < 0){
	  al = alpha;
	  fp_l = dh;
	}
	else{
	  ah = alpha;
	  fp_h = dh;
	}
	// alpha = (al*fp_h- ah*fp_l)/(fp_h- fp_l );
	// printf("bracketing : al = %e ; ah = %e  fp_l = %e ; fp_h = %e      alpha %e\n",al,ah,  fp_l, fp_h,  alpha);
	// printf(" iter %d \n", iter);
      }
      alpha = (al*fp_h- ah*fp_l)/(fp_h- fp_l );
    }
    a[0] = al;
    a[1] = alpha;
    a[2] = ah;
    //fprintf(log,"alpha = %20.18Le\n",alpha);
    return ;
  }
  
  void call_kern_createapply_projector_coalesced( int ncomps, float * d_w, float *d_p, float *d_dw,
						  int *d_invertiti, float weight,
						  float *d_dp, float *d_dfprojected, float *d_df,
						  float L ,
						  float &scal_ddp_ddfprojected , float &scal_ddp_dp)  {
    int totpatch = ncomps;
    dim3 blk = dim3( WKSIZE , 1 , 1 );
    int nb =   min(  iDivUp(totpatch, WKSIZE), WKSIZE )  ;
    dim3 grd = dim3( nb , 1, 1 );
    
    kern_createapply_projector_pass2_coalesced<<<grd,blk>>>( totpatch, d_w, d_p, d_dw,
							     d_invertiti,  weight,
							     d_dp, d_dfprojected, d_df,
							     d_forreduce  , d_forreduce1
							     );
    
    scal_ddp_ddfprojected  = cublasSdot(nb , d_ones, 0, d_forreduce  , 1);
    scal_ddp_dp            = cublasSdot(nb , d_ones, 0, d_forreduce1 , 1);
    
  }
  
  


  float complete_gradient_l1_andsum(  float *coeffs  , float * grad_tot , float *grad , float weight ) {

    int totpatch = pg2d.np*ncomps;

    dim3 blk = dim3( WKSIZE , 1 , 1 );
    int nb =   min(  iDivUp(totpatch, WKSIZE), WKSIZE )  ;
    dim3 grd = dim3( nb , 1, 1 );

    kern_complete_grad_l1_andsum<<<grd,blk>>>( totpatch, coeffs , grad_tot , grad ,    weight, d_forreduce);

    CUDACHECK;

    float penality = cublasSdot(nb, d_ones, 0, d_forreduce, 1);
    return penality;

  }


  void call_kern_dp_and_velocita(  float beta , float * d_dfprojected, float *  d_df, float *  d_dw,
				   float *  d_p,
				   float &velocita, float &velocita_q) {
    dim3 blk,grd;
    int totpatch = pg2d.np*ncomps;

    blk = dim3( WKSIZE , 1 , 1 );

    int nb =    min(  iDivUp(totpatch, WKSIZE), WKSIZE )  ;
    grd = dim3( nb , 1, 1 );
    kern_dp_and_velocita<<<grd,blk>>>( totpatch, d_dfprojected,  d_df, d_dw,  d_p ,  beta  ,
				       d_forreduce,  d_forreduce1 );

    CUDACHECK;

    velocita  = cublasSdot( nb, d_ones, 0, d_forreduce, 1);
    velocita_q  = cublasSdot( nb, d_ones, 0, d_forreduce1, 1);
  }

  float  call_kern_diff_pen_l1( int ncomps ,  float *coeffs  , float * d_p ,  float alpha , float weight ) {

    dim3 blk,grd;
    int totpatch = ncomps;

    blk = dim3( WKSIZE , 1 , 1 );

    int nb =   min(  iDivUp(totpatch, WKSIZE), WKSIZE )  ;
    grd = dim3( nb , 1, 1 );

    // printf(" nb %d %d \n", nb, WKSIZE);

    kern_diff_pen_l1<<<grd,blk>>>( totpatch, coeffs , d_p ,  alpha ,  weight, d_forreduce);

    CUDACHECK;
    float res;
    res=cublasSdot(  nb  , d_ones , 0, d_forreduce , 1)     ;
    return res;
  }
  float  call_kern_diffpenl1mult( int ncomps ,  float *coeffs  , float * d_p ,  float alpha , float weight, float *mult ) {

    dim3 blk,grd;
    int totpatch = ncomps;

    blk = dim3( WKSIZE , 1 , 1 );

    int nb =   min(  iDivUp(totpatch, WKSIZE), WKSIZE )  ;
    grd = dim3( nb , 1, 1 );

    // printf(" nb %d %d \n", nb, WKSIZE);

    kern_diff_pen_l1_mult<<<grd,blk>>>( totpatch, coeffs , d_p ,  alpha ,  weight, d_forreduce, mult);

    CUDACHECK;
    float res;
    res=cublasSdot(  nb  , d_ones , 0, d_forreduce , 1)     ;
    return res;
  }

  float  DDcall_kern_diffpenl1mult( int ncomps ,  float *coeffs  , float * d_p ,  float alpha , float weight, float *mult ) {


    dim3 blk,grd;
    int totpatch = ncomps;

    blk = dim3( WKSIZE , 1 , 1 );

    int nb =   min(  iDivUp(totpatch, WKSIZE), WKSIZE )  ;
    grd = dim3( nb , 1, 1 );

    // printf(" nb %d %d \n", nb, WKSIZE);

    DDkern_diff_pen_l1_mult<<<grd,blk>>>( totpatch, coeffs , d_p ,  alpha ,  weight, DDd_forreduce, mult);

    CUDACHECK;
    float res;
    res=cublasDdot(  nb  , DDd_ones , 0, DDd_forreduce , 1)     ;
    return res;
  }

  float DDcublasSdot(  int ncomps    , float *d_dw , int n1, float *d_p , int n2)     {



#ifdef USEDD
    return   cublasSdot(   ncomps    , d_dw ,  n1, d_p ,  n2)   ;
#endif


    assert(n1==1);
    assert(n2==1);

    dim3 blk,grd;
    int totpatch = ncomps;
    blk = dim3( WKSIZE , 1 , 1 );
    int nb =   min(  iDivUp(totpatch, WKSIZE), WKSIZE )  ;
    grd = dim3( nb , 1, 1 );
    CUDACHECK;
    DDkern_cublasSdot<<<grd,blk>>>( totpatch, d_dw,d_p , DDd_forreduce);

    CUDACHECK;
    float res;
    res=cublasDdot(  nb  , DDd_ones , 0, DDd_forreduce , 1)     ;
    return res;
  }

  void copia_dws (float *A, float *B,
		  float *hA=NULL, float *hB=NULL) {
    CUDA_SAFE_CALL( cudaMemcpy( A    ,  B  ,  pg2d.np*ncomps*sizeof(float), cudaMemcpyDeviceToDevice));
    if(hA) {
      memcpy(hA, hB,   multirings_size *sizeof(float) );
    }
  }

  void reinit_packet(float * d_y , float * d_packet , int nb_max){
    CUDA_SAFE_CALL( cudaMemset(   d_packet  , 0, pg2d.np*ncomps*nb_max*sizeof(float)));
    CUDA_SAFE_CALL( cudaMemcpy( d_packet , d_y , pg2d.np*ncomps*sizeof(float),cudaMemcpyDeviceToDevice));
  }


  void fourier_forward_projector(float *slice, float *sinogram, ParamsForTomo &p4t) {
    dim3 dimZeropadSliceGrid(p4t.ctxstruct->dfp_params.zeropad_grid_cols, p4t.ctxstruct->dfp_params.zeropad_grid_rows);
    dim3 dimSwapGrid(p4t.ctxstruct->dfp_params.swap_grid_cols, p4t.ctxstruct->dfp_params.swap_grid_rows);
    dim3 dimDfpGrid(p4t.ctxstruct->dfp_params.dfp_grid_cols, p4t.ctxstruct->dfp_params.dfp_grid_rows);
    dim3 dimShiftComplexGrid(p4t.ctxstruct->dfp_params.shift_grid_cols, p4t.ctxstruct->dfp_params.shift_grid_rows);
    dim3 dimCropSinoGrid(p4t.ctxstruct->dfp_params.crop_grid_cols, p4t.ctxstruct->dfp_params.crop_grid_rows);
    dim3 dimBlock(BLOCK_SIZE, BLOCK_SIZE);

    zeropadding_slice<<<dimZeropadSliceGrid,dimBlock>>>(slice,
							p4t.ctxstruct->dfp_params.gpu_zeropadded_slice,
							p4t.ctxstruct->dfp_params.slice_size_x,
							p4t.ctxstruct->dfp_params.rho_ext_len,
							p4t.ctxstruct->dfp_params.offset_x,
							p4t.ctxstruct->dfp_params.offset_y);

    swap_full_quadrants_complex<<<dimSwapGrid,dimBlock>>>(p4t.ctxstruct->dfp_params.gpu_zeropadded_slice);

    CUDA_SAFE_FFT(cufftExecC2C(p4t.ctxstruct->dfp_params.fft2d_plan,
			       (cufftComplex *)p4t.ctxstruct->dfp_params.gpu_zeropadded_slice,
			       (cufftComplex *)p4t.ctxstruct->dfp_params.gpu_zeropadded_slice,
			       CUFFT_FORWARD));

    swap_full_quadrants_complex<<<dimSwapGrid,dimBlock>>>(p4t.ctxstruct->dfp_params.gpu_zeropadded_slice);

    dfp_cuda_sinc_projector<<<dimDfpGrid,dimBlock>>>(p4t.ctxstruct->dfp_params.L2,
						     p4t.ctxstruct->dfp_params.rho_ext_len,
						     p4t.ctxstruct->dfp_params.rho_ext_len,
						     p4t.ctxstruct->dfp_params.angle_step_rad,
						     p4t.ctxstruct->dfp_params.table_spacing,
						     p4t.ctxstruct->dfp_params.theta_len,
						     p4t.ctxstruct->dfp_params.rho_ext_len,
						     p4t.ctxstruct->dfp_params.rho_ext_len/2,
						     p4t.ctxstruct->dfp_params.ktbl_len2,
						     p4t.ctxstruct->dfp_params.gpu_zeropadded_slice,
						     p4t.ctxstruct->dfp_params.gpu_reconstructed_sinogram);

    CUDA_SAFE_FFT(cufftExecC2C(p4t.ctxstruct->dfp_params.ifft1d_plan,
			       (cufftComplex *)p4t.ctxstruct->dfp_params.gpu_reconstructed_sinogram,
			       (cufftComplex *)p4t.ctxstruct->dfp_params.gpu_reconstructed_sinogram,
			       CUFFT_INVERSE));

    fftshift_c2r<<<dimShiftComplexGrid,dimBlock>>>(p4t.ctxstruct->dfp_params.gpu_reconstructed_sinogram,
						   p4t.ctxstruct->dfp_params.gpu_reconstructed_sinogram_real,
						   p4t.ctxstruct->dfp_params.rho_ext_len);

    crop_region<<<dimCropSinoGrid,dimBlock>>>(p4t.ctxstruct->dfp_params.gpu_reconstructed_sinogram_real,
					      sinogram,
					      p4t.ctxstruct->dfp_params.rho_ext_len,
					      p4t.ctxstruct->dfp_params.rho_len,
					      (p4t.ctxstruct->dfp_params.rho_ext_len - p4t.ctxstruct->dfp_params.rho_len) / 2,
					      0);
  }

  void fourier_backward_projector(float *sinogram, float *slice, ParamsForTomo &p4t) {
    dim3 dimPadGrid(p4t.ctxstruct->dfi_params.pps_grid_cols, p4t.ctxstruct->dfi_params.pps_grid_rows);
    dim3 dimInterpGrid(p4t.ctxstruct->dfi_params.interp_grid_cols, p4t.ctxstruct->dfi_params.interp_grid_rows);
    dim3 dimSwapGrid(p4t.ctxstruct->dfi_params.swap_grid_cols, p4t.ctxstruct->dfi_params.swap_grid_rows);
    dim3 dimSwapQuadsGrid(p4t.ctxstruct->dfi_params.swap_quad_grid_cols, p4t.ctxstruct->dfi_params.swap_quad_grid_rows);
    dim3 dimRoiGrid(p4t.ctxstruct->dfi_params.roi_grid_cols, p4t.ctxstruct->dfi_params.roi_grid_rows);
    dim3 dimBlock(BLOCK_SIZE, BLOCK_SIZE);


    CUDA_SAFE_CALL(cudaMemcpy2D(p4t.ctxstruct->dfi_params.gpu_truncated_sino,
				p4t.ctxstruct->dfi_params.rho_len * sizeof(float),
				sinogram,
				p4t.ctxstruct->dim_fft * sizeof(float),
				p4t.ctxstruct->dfi_params.rho_len * sizeof(float),
				p4t.ctxstruct->dfi_params.nprojs_span,
				cudaMemcpyHostToDevice));

    if (p4t.ctxstruct->DFI_R2C_MODE) {
      dfi_cuda_zeropadding_real<<<dimPadGrid,dimBlock>>>(p4t.ctxstruct->dfi_params.gpu_truncated_sino,
							 p4t.ctxstruct->dfi_params.rho_len,
							 p4t.ctxstruct->dfi_params.fft_sino_dim,
							 p4t.ctxstruct->dfi_params.gpu_zeropadded_sino);

      CUDA_SAFE_FFT(cufftExecR2C(p4t.ctxstruct->dfi_params.fft1d_plan,
				 (cufftReal *)p4t.ctxstruct->dfi_params.gpu_zeropadded_sino,
				 (cufftComplex *)p4t.ctxstruct->dfi_params.gpu_zeropadded_sino));
    }
    else {
      dfi_cuda_zeropadding_complex<<<dimPadGrid,dimBlock>>>((cufftReal *)p4t.ctxstruct->dfi_params.gpu_truncated_sino,
							    p4t.ctxstruct->dfi_params.rho_len,
							    (cufftComplex *)p4t.ctxstruct->dfi_params.gpu_input);

      CUDA_SAFE_FFT(cufftExecC2C(p4t.ctxstruct->dfi_params.fft1d_plan,
				 (cufftComplex *)p4t.ctxstruct->dfi_params.gpu_input,
				 (cufftComplex *)p4t.ctxstruct->dfi_params.gpu_input,
				 CUFFT_FORWARD));
    }

    if (p4t.ctxstruct->DFI_R2C_MODE) {
      CUDA_SAFE_CALL(cudaMallocArray((cudaArray**)&(p4t.ctxstruct->dfi_params.gpu_input_cua),
				     &(dfi_tex_projections.channelDesc),
				     p4t.ctxstruct->dfi_params.fft_sino_dim/2,
				     p4t.ctxstruct->dfi_params.nprojs_span));

      CUDA_SAFE_CALL(cudaMemcpy2DToArray((cudaArray*)p4t.ctxstruct->dfi_params.gpu_input_cua,
					 0,
					 0,
					 p4t.ctxstruct->dfi_params.gpu_zeropadded_sino,
					 p4t.ctxstruct->dfi_params.fft_sino_dim / 2 * sizeof(cufftComplex),
					 p4t.ctxstruct->dfi_params.fft_sino_dim / 2 * sizeof(cufftComplex),
					 p4t.ctxstruct->dfi_params.nprojs_span,
					 cudaMemcpyHostToDevice));

      CUDA_SAFE_CALL(cudaBindTextureToArray(dfi_tex_projections, (cudaArray*)p4t.ctxstruct->dfi_params.gpu_input_cua));
    }
    else {
      CUDA_SAFE_CALL(cudaMallocArray((cudaArray**)&(p4t.ctxstruct->dfi_params.gpu_input_cua),
				     &(dfi_tex_projections.channelDesc),
				     p4t.ctxstruct->dfi_params.rho_ext_len,
				     p4t.ctxstruct->dfi_params.nprojs_span));

      CUDA_SAFE_CALL(cudaMemcpy2DToArray((cudaArray*)p4t.ctxstruct->dfi_params.gpu_input_cua,
					 0,
					 0,
					 p4t.ctxstruct->dfi_params.gpu_input,
					 p4t.ctxstruct->dfi_params.rho_ext_len * sizeof(cufftComplex),
					 p4t.ctxstruct->dfi_params.rho_ext_len * sizeof(cufftComplex),
					 p4t.ctxstruct->dfi_params.nprojs_span,
					 cudaMemcpyHostToDevice));

      CUDA_SAFE_CALL(cudaBindTextureToArray(dfi_tex_projections, (cudaArray*)p4t.ctxstruct->dfi_params.gpu_input_cua));
    }

    CUDA_SAFE_CALL(cudaMallocArray((cudaArray**)&(p4t.ctxstruct->dfi_params.gpu_ktbl),
				   &(dfi_tex_ktbl.channelDesc),
				   p4t.ctxstruct->dfi_params.ktbl_len));

    CUDA_SAFE_CALL(cudaMemcpyToArray((cudaArray*)p4t.ctxstruct->dfi_params.gpu_ktbl,
				     0,
				     0,
				     p4t.ctxstruct->dfi_params.ktbl,
				     (p4t.ctxstruct->dfi_params.ktbl_len) * sizeof(float),
				     cudaMemcpyHostToDevice));

    CUDA_SAFE_CALL(cudaBindTextureToArray(dfi_tex_ktbl, (cudaArray*)p4t.ctxstruct->dfi_params.gpu_ktbl));

    dfi_cuda_interpolation_sinc<<<dimInterpGrid,dimBlock>>>(p4t.ctxstruct->dfi_params.spectrum_offset_y,
							    p4t.ctxstruct->dfi_params.L2,
							    p4t.ctxstruct->dfi_params.ktbl_len2,
							    p4t.ctxstruct->dfi_params.raster_size,
							    p4t.ctxstruct->dfi_params.raster_size2,
							    p4t.ctxstruct->dfi_params.table_spacing,
							    p4t.ctxstruct->dfi_params.angle_step_rad,
							    p4t.ctxstruct->dfi_params.theta_max,
							    p4t.ctxstruct->dfi_params.rho_max,
							    (p4t.ctxstruct->dfi_params.raster_size/2 + 1),
							    p4t.ctxstruct->dfi_params.max_radius,
							    p4t.ctxstruct->dfi_params.gpu_spectrum);

    cudaUnbindTexture(dfi_tex_projections);
    cudaUnbindTexture(dfi_tex_ktbl);

    dfi_cuda_swap_quadrants_complex<<<dimSwapGrid,dimBlock>>>(p4t.ctxstruct->dfi_params.gpu_spectrum,
							      p4t.ctxstruct->dfi_params.gpu_swapped_spectrum,
							      (p4t.ctxstruct->dfi_params.raster_size/2 + 1));
    cufftExecC2R(p4t.ctxstruct->dfi_params.ifft2d_plan,
		 p4t.ctxstruct->dfi_params.gpu_swapped_spectrum,
		 p4t.ctxstruct->dfi_params.gpu_c2r_result);

    dfi_cuda_swap_quadrants_real<<<dimSwapQuadsGrid,dimBlock>>>(p4t.ctxstruct->dfi_params.gpu_c2r_result);

    dfi_cuda_crop_roi<<<dimRoiGrid,dimBlock>>>(p4t.ctxstruct->dfi_params.gpu_c2r_result,
					       p4t.ctxstruct->dfi_params.roi_start_x,
					       p4t.ctxstruct->dfi_params.roi_start_y,
					       p4t.ctxstruct->dfi_params.roi_x,
					       p4t.ctxstruct->dfi_params.roi_y,
					       p4t.ctxstruct->dfi_params.raster_size,
					       p4t.ctxstruct->dfi_params.scale,
					       p4t.ctxstruct->dfi_params.gpu_output);

    cudaMemset(p4t.ctxstruct->dfi_params.gpu_swapped_spectrum,
	       0, (p4t.ctxstruct->dfi_params.raster_size/2 + 1) * p4t.ctxstruct->dfi_params.raster_size * sizeof(cufftComplex));

    cudaMemset(p4t.ctxstruct->dfi_params.gpu_spectrum,
	       0,
	       (p4t.ctxstruct->dfi_params.raster_size/2 + 1) * p4t.ctxstruct->dfi_params.raster_size * sizeof(cufftComplex));

    cudaMemcpy(slice,
	       p4t.ctxstruct->dfi_params.gpu_output,
	       p4t.ctxstruct->dfi_params.roi_x * p4t.ctxstruct->dfi_params.roi_y * sizeof(float), cudaMemcpyDeviceToHost);

  }

  float  calculate_grad_error(float * d_grad, float *d_coeffs, float peso_overlap,ParamsForTomo &p4t,
			      float *h_drings=NULL, float *h_rings=NULL) {


    //d_patches = d_X * d_coeffs^T  | (sizepatch,ncomps)*(pg2d.np,ncomps)^T

    //d_patches = d_X * d_coeffs^T  | (sizepatch,ncomps)*(pg2d.np,ncomps)^T
    {
      float alpha=1.0f, beta=0.0f;
      CUDACHECK;
      cublasSgemm('N','T',sizepatch , pg2d.np ,ncomps,
		  alpha,
		  d_X , sizepatch,
		  d_coeffs , pg2d.np,
		  beta,
		  d_patches,sizepatch );
      CUDACHECK;
    }

    {
      dim3 blk,grd;
      blk = dim3( 32 , 8 , 1 );
      grd = dim3( iDivUp(pg2d.pgx.dim  ,32) , iDivUp(pg2d.pgy.dim  ,8)  , 1 );
      put_patches_onimage_andsum_kernel<<<grd,blk>>>( d_image  , d_image_sum, d_patches   , pg2d  );
    }
    CUDACHECK;

    assert( pg2d.pgx.dim   ==  pg2d.pgy.dim     );
    int dimslice = pg2d.pgx.dim ;

    int sinosize = pg2d.doppio* p4t.ctxstruct->num_bins*p4t.ctxstruct->nprojs_span  ;

    //see C_HST_PROJECT_1OVER_GPU in c_hst_project_1over.cu
    //for each sinogram
    static int icount=0;
    for(int idop = 0 ; idop < pg2d.doppio ; idop++, icount++) {

      int memisonhost=0;

      if (p4t.ctxstruct->USE_DFP) {
        puts("DFP !!");
        fourier_forward_projector(d_image + idop * pg2d.pgy.dim * pg2d.pgx.dim,
                                  d_sino + idop * p4t.ctxstruct->num_bins * p4t.ctxstruct->nprojs_span,
                                  p4t);

	//        size_t size_orig_bytes = p4t.ctxstruct->num_bins * p4t.ctxstruct->nprojs_span * sizeof(float);
	//        float *original_fp = (float *)malloc(size_orig_bytes);
	//        CUDA_SAFE_CALL(cudaMemcpy(original_fp,
	//                                  d_sino + idop * p4t.ctxstruct->num_bins * p4t.ctxstruct->nprojs_span,
	//                                  size_orig_bytes,
	//                                  cudaMemcpyDeviceToHost));
	//        FILE *orig_sino_file = fopen("B4.raw", "wb");
	//        fwrite(original_fp, sizeof(float),p4t.ctxstruct->num_bins * p4t.ctxstruct->nprojs_span , orig_sino_file);
	//        fclose(orig_sino_file);
	//        exit(0);
      }


      else {
        p4t.ctxstruct->gpu_project(p4t.ctxstruct->gpuctx,
				   p4t.ctxstruct->num_bins,
				   p4t.ctxstruct->nprojs_span,
				   p4t.ctxstruct->angles_per_proj,
				   p4t.ctxstruct->axis_position  ,
				   
				   this->d_sino    + idop*p4t.ctxstruct->num_bins*p4t.ctxstruct->nprojs_span  ,
				   this->d_image   + idop * pg2d.pgy.dim * pg2d.pgx.dim  ,
				   
				   dimslice,
				   p4t.ctxstruct->axis_corrections,
				   p4t.ctxstruct->gpu_offset_x ,
				   p4t.ctxstruct->gpu_offset_y ,
				   p4t.ctxstruct->JOSEPHNOCLIP,
				   p4t.DETECTOR_DUTY_RATIO        ,
				   p4t.DETECTOR_DUTY_OVERSAMPLING,
				   memisonhost, p4t.ctxstruct->FAN_FACTOR,
				   p4t.ctxstruct->SOURCE_X
				   );
      }




    }

    CUDACHECK;

    if(terminenoto) {
      CUDA_SAFE_CALL( cudaMemcpy( this->d_sino_error,this->d_sino_data, sinosize *sizeof(float),cudaMemcpyDeviceToDevice));
    } else {
      
      CUDA_SAFE_CALL( cudaMemset(   this->d_sino_error   , 0, sinosize *sizeof(float)      ));
    }
    
    {
      float alpha= -1.0f;
      cublasSaxpy ( sinosize ,  alpha, d_sino, 1,d_sino_error , 1);
    }
    
    
    if(h_rings) {
      float my_rings_tmp[ this->multirings_size   ];
      if (this->iterringsize) {
	convolve_unsharp( my_rings_tmp, h_rings, kernel2, rkern, pg2d.doppio* p4t.ctxstruct->NUMBER_OF_RINGS, this->num_bins);
      } else {
	memcpy(my_rings_tmp, h_rings,  this->multirings_size  *sizeof(float) ) ;
      }
      
      CUDA_SAFE_CALL( cudaMemcpy( this->D_rings,
				  my_rings_tmp,
				  this->multirings_size  *sizeof(float),
				  cudaMemcpyHostToDevice));
      
      
      if(p4t.ctxstruct->NUMBER_OF_RINGS==1) {
	cublasSscal( this->multirings_size ,
		     p4t.ctxstruct->RING_ALPHA ,
		     this->D_rings , 1);
      } else {
	int nrings = p4t.ctxstruct->NUMBER_OF_RINGS;
	for (int i=0; i<nrings; i++) {
	  cublasSscal( this->num_bins*pg2d.doppio ,
		       p4t.ctxstruct->RING_ALPHA / this->alpharings_multi[i] ,
		       this->D_rings + i*this->num_bins*pg2d.doppio,
		       1);
	}
      }
      CUDACHECK;
      
      {
	dim3 blk,grd;
	blk = dim3( 32 , 16 , 1 );
	grd = dim3(  iDivUp(p4t.ctxstruct->num_bins , 32 )    ,
		     iDivUp(pg2d.doppio *p4t.ctxstruct->nprojs_span  , 16 )   , 1 );
	if( p4t.ctxstruct->VECTORIALITY==1   ) {
	  if (p4t.ctxstruct->NUMBER_OF_RINGS==1) {
	    rows_subtract_kernel<<<grd,blk>>>( this->d_sino_error,
					       this->D_rings,
                 pg2d.doppio, // supposed to be 1 ?
					       p4t.ctxstruct->nprojs_span,
					       p4t.ctxstruct->num_bins
					       );
	  } else {
	    rows_subtract_kernel_mw<<<grd,blk>>>( this->d_sino_error,
						  this->D_rings,
						  pg2d.doppio,
						  p4t.ctxstruct->nprojs_span,
						  p4t.ctxstruct->num_bins,
						  p4t.ctxstruct->NUMBER_OF_RINGS,
						  this->w_step,
						  this->d_interp_wavelet
						  );
	  }
	} else {
	  if (p4t.ctxstruct->NUMBER_OF_RINGS==1) {
	    rows_subtract_kernel_vectoriality <<<grd,blk>>>( this->d_sino_error,
							     this->D_rings,
							     pg2d.doppio,
							     p4t.ctxstruct->nprojs_span,
							     p4t.ctxstruct->num_bins,
                   this->d_angles_per_proj // projection angles (rad)
							     );
	  } else {
//	    fprintf(stderr,"ERROR: multirings ( wavelet interpolation) not yet implemented for vectorial case\n");
      rows_subtract_kernel_mw_vectoriality<<<grd,blk>>>(this->d_sino_error,
                                                        this->D_rings,
                                                        pg2d.doppio,
                                                        p4t.ctxstruct->nprojs_span,
                                                        p4t.ctxstruct->num_bins,
                                                        p4t.ctxstruct->NUMBER_OF_RINGS,
                                                        this->w_step,
                                                        this->d_interp_wavelet,
                                                        this->d_angles_per_proj
                                                        );
	  }
	}
      }



      

      CUDA_SAFE_CALL( cudaMemset( this->D_rings, 0, this->multirings_size  *sizeof(float)));
      {
	dim3 blk,grd;
	blk = dim3( 32 , 16 , 1 );
	grd = dim3(  iDivUp(p4t.ctxstruct->num_bins , 32 )    ,
		     iDivUp(pg2d.doppio   , 16 )   , 1 );
	if(p4t.ctxstruct->VECTORIALITY==1) {
	  if (p4t.ctxstruct->NUMBER_OF_RINGS>1) {
	    rows_reduce_kernel_mw<<<grd,blk>>>( this->d_sino_error,
						this->D_rings,
						pg2d.doppio,
						p4t.ctxstruct->nprojs_span,
						p4t.ctxstruct->num_bins,
						p4t.ctxstruct->NUMBER_OF_RINGS,
						this->w_step,
						this->d_interp_wavelet
						); CUDACHECK;
	  } else{
	    rows_reduce_kernel<<<grd,blk>>>( this->d_sino_error,
					     this->D_rings,
					     pg2d.doppio,
					     p4t.ctxstruct->nprojs_span,
					     p4t.ctxstruct->num_bins
					     );
	  }
	} else {
	  if (p4t.ctxstruct->NUMBER_OF_RINGS==1) {
	    rows_reduce_kernel_vectoriality<<<grd,blk>>>( this->d_sino_error,
							  this->D_rings,
							  pg2d.doppio,
							  p4t.ctxstruct->nprojs_span,
							  p4t.ctxstruct->num_bins,
							  this->d_angles_per_proj
							  );
	  } else {
//	    fprintf(stderr,"ERROR(bis): multirings ( wavelet interpolation) not yet implemented for vectorial case\n");
//	    exit(1);
      rows_reduce_kernel_mw_vectoriality<<<grd,blk>>>(this->d_sino_error,
                                         this->D_rings,
                                         pg2d.doppio,
                                         p4t.ctxstruct->nprojs_span,
                                         p4t.ctxstruct->num_bins,
                                         p4t.ctxstruct->NUMBER_OF_RINGS,
                                         this->w_step,
                                         this->d_interp_wavelet,
                                         this->d_angles_per_proj
                                         );


	  }
	}
      }




      // if (0) {//convolution by the unsharp mask...
      //   int doppio = pg2d.doppio  ;
      //   int num_bins =   p4t.ctxstruct->num_bins ;
      //   int id,i,j;
      //   for(id=0; id<doppio; id++) {
      //      for(i=0; i< num_bins*nwavelets; i++) ringstmp[i]=0;
      //      for(i=0; i< num_bins*nwavelets; i++){
      //        for(j=max(0,(i-2*rkern)); j<=min((num_bins*nwavelets-1), (i+2*rkern)) ; j++   ) {
      //          ringstmp[i] +=   kernel2[2*rkern + i-j] * h_drings[id*num_bins*nwavelets + j]  ;
      //        }
      //      }
      //      for(i=0; i< num_bins*nwavelets; i++)  {
      //        h_drings[id*num_bins*nwavelets + i]   =   ringstmp[i]  ;
      //      }
      //   }
      // }



      //cublasSscal(multirings_size, p4t.ctxstruct->RING_ALPHA, D_rings, 1);
      if(p4t.ctxstruct->NUMBER_OF_RINGS==1) {
	cublasSscal( this->multirings_size ,
		     p4t.ctxstruct->RING_ALPHA ,
		     this->D_rings , 1);
      } else {
	int nrings = p4t.ctxstruct->NUMBER_OF_RINGS;
	for (int i=0; i<nrings; i++) {
	  cublasSscal( this->num_bins*pg2d.doppio ,
		       p4t.ctxstruct->RING_ALPHA / this->alpharings_multi[i] ,
		       this->D_rings + i*this->num_bins*pg2d.doppio,
		       1);
	}
      }



      if (this->iterringsize*0) {
	float my_rings_tmp[ this->multirings_size   ];
	CUDA_SAFE_CALL( cudaMemcpy(my_rings_tmp ,this->D_rings, this->multirings_size  *sizeof(float),cudaMemcpyDeviceToHost));
	convolve_unsharp( h_drings,my_rings_tmp,  kernel2, rkern, pg2d.doppio* p4t.ctxstruct->NUMBER_OF_RINGS, this->num_bins);
      } else {
	CUDA_SAFE_CALL( cudaMemcpy( h_drings,this->D_rings, this->multirings_size  *sizeof(float),cudaMemcpyDeviceToHost));
      }
    }
    float renorm = 1.0f ;
    {
      /* Historique des facteurs de normalisation :

	 --   a partir de I/I_0 on passe par le -log et on a les donnÃ©es des l' absorption totale.

	 -- Ces dernieres sont multipliees par pi/2 *1/npjs *1/pixelsize_en_cm,
	 On appellera le rÃ©sultat data

	 -- le choix de data est tel, que si on le passe par le filtre rampe et une back-projection
	 ca va donner la slice reconstruite

	 Maintenant allons voir ce qui se passe pour la projection:

	 -- La projection produit des rÃ©sultat que on peut comparer directement a data:
	 Pour faire cela on somme les valeurs des pixels le long
	 de la ligne d' intÃ©gration, pondÃ©rÃ©s pat la longueur d' intersection
	 qui est egal a 1 si on traverse parallÃ¨lement aux cote.
	 Le tout est multiplie' par pi/(2npjs)

	 -- Quand on ecrit le rÃ©sultat on n' ecrit pas le resultat ci dessus
	 mais on le multiplie d' abord par fact pour avoir le -log de I/I0 :
	 fact= 1/ (PI/2/npjs)* (pixelsize_en_cm).
	 C' est a dire on revient en arriÃ¨re au point 1


	 Concernant les methodes iteratives :

	 CAS NON PRECONDITIONNE'

	 -- on recoit data tel que explique ci dessu
	 -- Donc pour comparer, la projection est fait comme ci dessus
	 Joseph tout simple avec pixelsize=1 le tout multiplie' par pi/2/npjs
	 --  Donc, quand on considere (projection-data) et on fait une BP tout simple
	 on obtient le gradient de quelque chose, mais comme BP manque d' un facteur pi/(2npjs)
	 pour etre le transpose de notre projection, ce quelque chose est
	 (projection-data)**2 /(pi/(2npjs))
	 Comme nous on veut le gradient de
	 (projection-data)**2
	 on multiplie d_sino_error=(projection-data)   par    (pi/(2npjs))
	 qui est le manque de BP

	 Ensuite , quand on fait d_sino_error**2
	 il faudra que on enleve (pi/(2npjs)) une fois ( car on vient de faire le carre )
	 pour avoir le bon fidelity qui depends de  (projection-data)**2

	 Du le nom facteur diabolique bien merite'
      */
      //The error has to be re-normalized so that fidelity=1/2*sum(err.^2)
      //If the pre-condition ramp filter is applied, this renormalization is already done
      renorm = 1.0f/(M_PI*0.5f/ p4t.ctxstruct->nprojs_span); //facteur diabolique
      if ( p4t.ctxstruct->DO_PRECONDITION != 1) {
        cublasSscal(sinosize,1.0/renorm ,d_sino_error , 1);
      }
    }
    float fidelity = 0.0f;

    //------------- PP.add : main    precondition --------------------


    if (p4t.ctxstruct->DO_PRECONDITION || p4t.ctxstruct->fai360==1 ) {
      float* d_tmp_arr;
      CUDA_SAFE_CALL(cudaMalloc(&d_tmp_arr,        num_bins*fftbunch*sizeof(cufftReal)));

      int doppio = pg2d.doppio;
      int sino_size = doppio*nprojs_span*num_bins;
      int dim_fft_ramp = (num_bins)/2+1;
      cufftReal* d_r_sino_error = (cufftReal*) p4t.ctxstruct->precond_params_dl.d_r_sino_error;
      cufftComplex* d_i_sino_error = (cufftComplex*) p4t.ctxstruct->precond_params_dl.d_i_sino_error;
      cufftHandle planRamp_forward = (cufftHandle) p4t.ctxstruct->precond_params_dl.planRamp_forward;
      cufftHandle planRamp_backward = (cufftHandle) p4t.ctxstruct->precond_params_dl.planRamp_backward;

      dim3 blk, grd;
      blk = dim3( blsize_cufft , blsize_cufft , 1 );
      grd = dim3( iDivUp(dim_fft_ramp ,blsize_cufft) , iDivUp(fftbunch ,blsize_cufft) , 1 );
      fidelity = 0.0f ;
      for (int offset = 0 ; offset < sino_size;  offset += num_bins*fftbunch)  {
        //The following memset is important for "weird" sizes...
        CUDA_SAFE_CALL(cudaMemset(d_r_sino_error, 0, fftbunch*(num_bins)*sizeof(cufftReal)));
        int numels = min(sino_size-offset,fftbunch*num_bins);


	//         CUDA_SAFE_CALL(cudaMemcpy2D(d_r_sino_error,nextpow2_padded(num_bins)*sizeof(cufftReal),
	//                                     d_sino_error+offset, num_bins*sizeof(cufftReal),
	//                                     num_bins*sizeof(cufftReal), numels/num_bins, cudaMemcpyDeviceToDevice));
	
        CUDA_SAFE_CALL(cudaMemcpy2D(d_r_sino_error,(num_bins)*sizeof(cufftReal),
                                    d_sino_error+offset, num_bins*sizeof(cufftReal),
                                    num_bins*sizeof(cufftReal), numels/num_bins, cudaMemcpyDeviceToDevice));
// 	{
// 	  dim3 blk, grd;
// 	  blk = dim3( blsize_cufft , blsize_cufft , 1 );
// 	  if(self->fai360==0) {
// 	    grd = dim3( iDivUp( nextpow2_padded(num_bins) - num_bins,blsize_cufft) , iDivUp( numels/num_bins,blsize_cufft) , 1 );
// 	    padda_kernel<<<grd,blk>>>( d_r_sino_error,num_bins,  nextpow2_padded(num_bins),    numels/num_bins     )  ;
// 	  } else {
// 	    grd = dim3( iDivUp( nextpow2_padded(num_bins)           ,blsize_cufft) , iDivUp( numels/num_bins,blsize_cufft) , 1 );
// 	    transition_kernel<<<grd,blk>>>( d_r_sino_error,num_bins,  nextpow2_padded(num_bins),    numels/num_bins  ,    self->axis_position  )  ;
// 	  }
// 	};
	CUDA_SAFE_CALL(cudaMemcpy(  d_tmp_arr   , d_r_sino_error,
				    (num_bins) *  numels/num_bins   * sizeof(cufftReal)  ,
				    cudaMemcpyDeviceToDevice ););
	
	if(p4t.ctxstruct->fai360)
	  {
	    dim3 blk, grd;
	    blk = dim3( blsize_cufft , blsize_cufft , 1 );
	    grd = dim3( iDivUp( (num_bins)           ,blsize_cufft) , iDivUp( numels/num_bins,blsize_cufft) , 1 );
	    transition_kernel<<<grd,blk>>>( d_r_sino_error,num_bins,  (num_bins),    numels/num_bins  ,    p4t.ctxstruct->axis_position  )  ;
	  }

	
	
	if(p4t.ctxstruct->DO_PRECONDITION ) {
	  
	  CUDA_SAFE_FFT(cufftExecR2C(planRamp_forward,(cufftReal *) d_r_sino_error,(cufftComplex *) d_i_sino_error));
	  
	  kern_fourier_filter<<<grd,blk>>>(d_i_sino_error, p4t.ctxstruct->precond_params_dl.filter_coeffs, dim_fft_ramp, fftbunch);
	  
	  CUDA_SAFE_FFT(cufftExecC2R(planRamp_backward,(cufftComplex *) d_i_sino_error,(cufftReal *) d_r_sino_error));
	}

	if(p4t.ctxstruct->fai360)
	  {
	    dim3 blk, grd;
	    blk = dim3( blsize_cufft , blsize_cufft , 1 );
	    grd = dim3( iDivUp( (num_bins)           ,blsize_cufft) , iDivUp( numels/num_bins,blsize_cufft) , 1 );
	    transition_kernel<<<grd,blk>>>( d_r_sino_error,num_bins,  (num_bins),    numels/num_bins  ,    p4t.ctxstruct->axis_position  )  ;
	  }


	fidelity  += cublasSdot ((numels/num_bins)*(num_bins),d_r_sino_error , 1, d_tmp_arr  , 1)/2.0;

        // CUDA_SAFE_CALL(cudaMemcpy2D(d_tmp_arr,nextpow2_padded(num_bins)*sizeof(cufftReal),
        //                             d_sino_error+offset, num_bins*sizeof(cufftReal),
        //                             num_bins*sizeof(cufftReal), numels/num_bins, cudaMemcpyDeviceToDevice));

        CUDA_SAFE_CALL(cudaMemcpy2D(
				    d_sino_error+offset, num_bins*sizeof(cufftReal),
				    d_r_sino_error  , (num_bins)*sizeof(cufftReal),
				    num_bins*sizeof(cufftReal), numels/num_bins, cudaMemcpyDeviceToDevice));

	// fidelity  += cublasSdot (numels, d_tmp_arr, 1, d_sino_error+offset   , 1)/2.0;

      } //end for

      if(h_rings) {
	for (int offset = 0 ; offset < this->multirings_size ;  offset += num_bins*fftbunch)  {
	  int numels = min(this->multirings_size-offset,fftbunch*num_bins);

	  // CUDA_SAFE_CALL(cudaMemset(d_r_sino_error, 0, nextpow2(rings_size)*sizeof(cufftReal))); //FIXME : test
	  // CUDA_SAFE_CALL(cudaMemcpy(d_r_sino_error,h_drings+offset, numels*sizeof(cufftReal), cudaMemcpyHostToDevice));

	  CUDA_SAFE_CALL(cudaMemcpy2D(d_r_sino_error,(num_bins)*sizeof(cufftReal),
				      h_drings+offset, num_bins*sizeof(cufftReal),
				      num_bins*sizeof(cufftReal), numels/num_bins, cudaMemcpyHostToDevice));
	  if(p4t.ctxstruct->fai360==1) {
	    dim3 blk, grd;
	    blk = dim3( blsize_cufft , blsize_cufft , 1 );
	    grd = dim3( iDivUp( (num_bins) ,blsize_cufft) , iDivUp( numels/num_bins,blsize_cufft) , 1 );
	    transition_kernel<<<grd,blk>>>( d_r_sino_error,num_bins,  (num_bins),    numels/num_bins  ,    p4t.ctxstruct->axis_position  )  ;
	  }
	  if(p4t.ctxstruct->DO_PRECONDITION ) {
	    CUDA_SAFE_FFT(cufftExecR2C(planRamp_forward,(cufftReal *) d_r_sino_error,(cufftComplex *) d_i_sino_error));
	    kern_fourier_filter<<<grd,blk>>>(d_i_sino_error, p4t.ctxstruct->precond_params_dl.filter_coeffs, dim_fft_ramp, fftbunch);
	    CUDA_SAFE_FFT(cufftExecC2R(planRamp_backward,(cufftComplex *) d_i_sino_error,(cufftReal *) d_r_sino_error));
	  }

	  if(p4t.ctxstruct->fai360==1) {
	    dim3 blk, grd;
	    blk = dim3( blsize_cufft , blsize_cufft , 1 );
	    grd = dim3( iDivUp( (num_bins) ,blsize_cufft) , iDivUp( numels/num_bins,blsize_cufft) , 1 );
	    transition_kernel<<<grd,blk>>>( d_r_sino_error,num_bins,  (num_bins),    numels/num_bins  ,    p4t.ctxstruct->axis_position  )  ;
	  }
	  
	  // CUDA_SAFE_CALL(cudaMemcpy(h_drings+offset,d_r_sino_error, numels*sizeof(cufftReal), cudaMemcpyDeviceToHost));
	  CUDA_SAFE_CALL(cudaMemcpy2D(
				      h_drings+offset, num_bins*sizeof(cufftReal),
				      d_r_sino_error  , (num_bins)*sizeof(cufftReal),
				      num_bins*sizeof(cufftReal), numels/num_bins, cudaMemcpyDeviceToHost));
	  
	}
	if(p4t.ctxstruct->DO_PRECONDITION ) {
	  for (int i=0; i<this->multirings_size; i++)  h_drings[i]*=renorm;        ;
	}
      }
      CUDA_SAFE_CALL(cudaFree(d_tmp_arr));
    } //end precond
    else {
      fidelity =  cublasSnrm2( sinosize  ,  d_sino_error   , 1);
      fidelity *= fidelity/2*renorm;
      if(h_rings) {
	// for (int i=0; i<this->multirings_size; i++)  h_drings[i]/=(renorm);        ;
      }
    }

    //------------- End of PP.add --------------------

    if(fidelity!=fidelity) fprintf(stderr,"fidelity est NaN\n");
    fidelity *= renorm;
    //------

    if (h_rings && this->iterringsize) {
      float my_rings_tmp[ this->multirings_size   ];
      memcpy(my_rings_tmp ,h_drings, this->multirings_size  *sizeof(float));
      convolve_unsharp( h_drings,my_rings_tmp,  kernel2, rkern, pg2d.doppio* p4t.ctxstruct->NUMBER_OF_RINGS, this->num_bins);
    }


    if (optim_algorithm == 5 && terminenoto == 0 && h_rings) {
      CUDA_SAFE_CALL( cudaMemcpy( D_rings, h_drings, multirings_size  *sizeof(float),cudaMemcpyHostToDevice)); //it should not affect, since D_rings is overwritten
      //save P(p_x)   (= d_sino_error at this stage) for later
    }
    //------


    {
      int do_precondition = 0 ;
      int memisonhost=0;

      if (p4t.ctxstruct->USE_DFI) {
        // est-ce que le preconditionnement a un sens pour DFI ?
        // TEST ---
	//        cublasSscal(sinosize, 1/(M_PI*0.5f/ p4t.ctxstruct->nprojs_span) ,d_sino_error , 1);
        //FIXME : the result has a very small scale ; have to rescale to be consistent with gpu_fbdl
        dfi_gpu_main(p4t.ctxstruct , d_sino_error , d_image_error, 0);
      }
      else {
        gpu_fbdl(p4t.ctxstruct , d_sino_error , d_image_error,  do_precondition,
		 p4t.DETECTOR_DUTY_RATIO,
		 p4t.DETECTOR_DUTY_OVERSAMPLING,
		 pg2d.doppio ,
		 memisonhost
		 );
      }

    }

    {
      dim3 blk,grd;
      int totpatch = pg2d.np*sizepatch;

      blk = dim3( WKSIZE , 1 , 1 );
      int done=0;

      while(done<totpatch) {

	//  if(done) printf("WARNING by pieces line %d \n", __LINE__);

	int nb =   min(  iDivUp(totpatch-done, WKSIZE), 0xFFFF )  ;
	int todo = min( nb* WKSIZE ,totpatch -done   ) ;

	grd = dim3( nb , 1, 1 );
	prepare_gradient_onpatches_kernel<<<grd,blk>>>( d_image, d_image_sum, d_image_error,
							d_patches, d_patches_gradient,
							pg2d , todo, done,
							peso_overlap);
	done+=todo;
      }
      float tmp =   cublasSnrm2( totpatch  ,  d_patches    , 1) ;
      fidelity += tmp*tmp*peso_overlap/2.0f ;
    }


    //d_dw = (patches_gradient)^T * X
    {
      float alpha= 1.0f;
      float beta = 0.0f;
      cublasSgemm('T','N',pg2d.np , ncomps ,  sizepatch ,
		  alpha,
		  d_patches_gradient    , sizepatch ,
		  d_X    , sizepatch  ,
		  beta,
		  d_grad  ,pg2d.np  );
    }

    return fidelity ;
  }




  //------------------------------------------------------

  float csg_fista(ParamsForTomo p4t, float Lipschitz, float beta_tv, int nsteps, float* y, float* x, float* x_old, float* grad, int np, int ncomps, int restart_if_increase) {

    dim3 blk, grd;
    grd = dim3( iDivUp(np ,blsize_cufft) , iDivUp(ncomps ,blsize_cufft) , 1 );
    blk = dim3( blsize_cufft , blsize_cufft , 1 );

    cudaMemcpy(x_old, y, np*ncomps*sizeof(float), cudaMemcpyDeviceToDevice);
    float t_old = 1.0f, t;
    float err = 0.0f, err_new = 0.0f;
    terminenoto=1; //take "data" into account when calculating gradient
    for (int k=0; k< nsteps; k++) {
      //calculate (quad) gradient at y
      err = calculate_grad_error(grad, y, p4t.ctxstruct->peso_overlap, p4t);
      //update "t" and variables
      t = (1+sqrtf(4*t_old*t_old+1))*0.5f;
      csg_kern_fista_update_variables<<<grd,blk>>>(x, x_old, y, grad, t, t_old, 1.0f/Lipschitz, beta_tv/Lipschitz, np, ncomps);
      if (restart_if_increase)  {
        err_new = calculate_grad_error(grad, y, p4t.ctxstruct->peso_overlap, p4t);
        if (err_new > err) {
          cudaMemcpy(y, x, np*ncomps*sizeof(float), cudaMemcpyDeviceToDevice);
          t = 1.0f;
          t_old = t;
        }
        else {
          t_old = t;
          cudaMemcpy(x_old, x, np*ncomps*sizeof(float), cudaMemcpyDeviceToDevice);
        }
      }
      else {
        t_old = t;
        cudaMemcpy(x_old, x, np*ncomps*sizeof(float), cudaMemcpyDeviceToDevice);
      }
    }
    return err;
  }


  //------------------------------------------------------


};



extern "C" {
  void fb_dl(Gpu_Context * self,
	     float *WORK ,
	     float *SLICE,
	     int    precondition,
	     float  DETECTOR_DUTY_RATIO,
	     int    DETECTOR_DUTY_OVERSAMPLING,
	     float  weight,
	     float *patches,
	     int    npatches,
	     int    dim_patches,
	     int    vectoriality,
	     float *Lipschitz,
	     void  (*proietta) (void *, float* , int  , float ) ) ;
}

void fb_dl(Gpu_Context * self,
           float *WORK ,
           float *SLICE,
           int    precondition,
           float  DETECTOR_DUTY_RATIO,
	   int    DETECTOR_DUTY_OVERSAMPLING,
           float  weight,
	   float *patches,
	   int    npatches,
	   int    dim_patches,
	   int    vectoriality,
	   float *Lipschitz,
	   void  (*proietta) (void *, float* , int  , float ) ) {


  FILE* log_fbdl = NULL;
  log_fbdl = fopen("log_fbdl.log","w");
  if(log_fbdl == NULL){
    fprintf(stderr,"pb dans l'ouverture du fichier log_fbdl");
  }


  fprintf(stderr,"\n\n\n\n\n\n =====================================\n entering fb_dl ==\n\n\n\n\n===========================\n\n\n");
  //if (self->ITER_RING_SIZE) printf("ITER_RING_SIZE = %f\n",self->ITER_RING_SIZE);
  cuCtxSetCurrent ( *((CUcontext *) self->gpuctx  ))  ;

  int dim0    = self->num_x;
  int dim1    = self->num_y;
  int doppio  = vectoriality ;
  int ncomps  = npatches;

  ParamsForTomo p4t  =  (ParamsForTomo)  { (Gpu_Context*) self, DETECTOR_DUTY_RATIO, DETECTOR_DUTY_OVERSAMPLING } ;

  Patches_Geometry2D   pg2d(dim0, dim1,    dim_patches,  self->STEPFORPATCHES , doppio   );
  Buffers  buffers(   pg2d , ncomps, patches, WORK, p4t , self->ITER_RING_HEIGHT, self->ITER_RING_SIZE,self->OPTIM_ALGORITHM ) ;
  buffers.set_w2zero(buffers.d_w , buffers.rings );

  buffers.terminenoto=1;

  float weight_rings = self->RING_BETA;

  //calculate Lipschitz constant
  if( *Lipschitz==-1) {

    buffers.calculate_grad_error( buffers.d_dw, buffers.d_w, self->peso_overlap  , p4t ,
				  buffers.drings    ,  buffers.rings ) ;


    buffers.rinormalizza_grad(buffers.d_dw, buffers.drings  );
    buffers.copia_dws( buffers.d_w, buffers.d_dw ,
		       buffers.rings    ,  buffers.drings);

    buffers.terminenoto=0;
    float norma=0;
    for(int i=0;i<self->LIPSCHITZ_ITERATIONS; i++) {
      buffers.calculate_grad_error(  buffers.d_dw, buffers.d_w,  self->peso_overlap   , p4t,
				     buffers.drings    ,  buffers.rings  ) ;
      norma=  buffers.rinormalizza_grad(buffers.d_dw, buffers.drings );
      buffers.copia_dws( buffers.d_w, buffers.d_dw ,
			 buffers.rings    ,  buffers.drings          );

      if(self->verbosity>0) printf(" Lipschitz %e \n",  norma );
    }

    *Lipschitz=norma*1.2; //TODO : LIPSCHITZFACTOR is in Cparameters
    return ; // RICORDARSI FARE DISTRUTTORE
  }

  float t = 1.0;


  FILE* fid_p;
  float* energy_vector;
  switch(self->OPTIM_ALGORITHM)
    {
    case 1:  // FISTA

      if(self->verbosity>0) {
	puts("FISTA main loop");
	if (buffers.rings) printf("Rings correction is ENABLED - using %d rings\n",p4t.ctxstruct->NUMBER_OF_RINGS);
	else puts("Rings correction is DISABLED");
      }

      //---
      buffers.set_w2zero(buffers.d_w, buffers.rings  );
      //for multirings
      if (p4t.ctxstruct->NUMBER_OF_RINGS > 1) memset(buffers.rings, 0, buffers.multirings_size*sizeof(float));
      //---
      buffers.terminenoto=1;
      energy_vector = (float*) calloc(self->ITERATIVE_CORRECTIONS,sizeof(float));
      for(int iter=0; iter < self->ITERATIVE_CORRECTIONS ; iter++) {

        float told  = t;
        t=(1.0f+sqrt(1.0f+4.0f*t*t))/2.0f;

        float sparsita  = 0.0f ;
        float L1        = 0.0f ;
        float error = buffers.calculate_grad_error( buffers.d_dw, buffers.d_w,   self->peso_overlap, p4t,
                                                    buffers.drings    ,  buffers.rings  ) ;
        L1       =   cublasSasum(pg2d.np*ncomps, buffers.d_w, 1 )   ;


        {
          dim3 blk,grd;
          int totcomp = pg2d.np*ncomps;
          blk = dim3( WKSIZE , 1 , 1 );
          int done=0;

          while(done<totcomp) {
            //      if(done) printf("WARNING by pieces line %d \n", __LINE__);

            int nb =   min(  iDivUp(totcomp-done, WKSIZE), 0xFFFF )  ;
            int todo = min( nb* WKSIZE ,totcomp -done   ) ;
            grd = dim3( nb , 1, 1 );

            if(self->PENALTY_TYPE == 1){
              kern_shrink_fista_compact_smooth<<<grd,blk>>>(  buffers.d_w,    buffers.d_dw , buffers.d_wold,
                                                              weight/(*Lipschitz), *Lipschitz,  buffers.d_notzero, told, t, todo, done ,
                                                              self->SMOOTH_PENALTY_PARAM);
            } else {
              kern_shrink_fista_compact_l1<<<grd,blk>>>(  buffers.d_w,    buffers.d_dw , buffers.d_wold,
                                                          weight/(*Lipschitz), *Lipschitz,  buffers.d_notzero, told, t, todo, done, iter );
            }
            done+=todo;
          }
          // if (self->PENALTY_TYPE == 0){
          sparsita =   cublasSasum(totcomp, buffers.d_notzero, 1 ) /totcomp     ;
	  //          L1       =   cublasSasum(totcomp, buffers.d_w, 1 )   ;
          // }
        }
        float rings_L1_norm = 0.0f;
        if(self->ITER_RING_HEIGHT){
          float  rold;
          for(int i=0; i<buffers.multirings_size; i++) {
            buffers.rings_tmp[i]  = buffers.rings[i] +  buffers.drings[i]/(*Lipschitz);
          }
          proietta( self->void_ccspace_ptr, buffers.rings_tmp, doppio  , (*Lipschitz) ) ;
          for(int i=0; i<buffers.multirings_size; i++) {
            rold  = buffers.rings_old[i];
            buffers.rings_old[i] = buffers. rings_tmp[i];
            buffers.rings[i]=buffers.rings_tmp[i] +  ((told-1)/t) /*(iter-1.0)/(iter+10.0)*/ * (buffers.rings_tmp[i]-rold) ;
            rings_L1_norm += fabsf(buffers.rings[i]);
          }
	  //          if (iter%50 == 0) qdplot(buffers.rings, self->num_bins*doppio*4);
        }
        energy_vector[iter] = error+L1*weight + self->RING_BETA*rings_L1_norm;
	if(self->verbosity>0) {
	  if(iter%10==0) printf(" ITER %d TOTALE %20.14e fidelity %e L1 %e rings %e sparsita %e \n", iter,  error+L1*weight + self->RING_BETA*rings_L1_norm, error, L1  , rings_L1_norm, sparsita  );
	}

      }
      fid_p = fopen("energy_dl_fista.dat","wb");
      fwrite(energy_vector, sizeof(float), self->ITERATIVE_CORRECTIONS, fid_p);
      fclose(fid_p);
      free(energy_vector);

      break; // end of FISTA main loop

    case 2: // CG-L1
      {
	if(self->verbosity>0)   printf("==============================  in fb_dl case 2  =================================\n");

	double beta;
	double alpha;
	double k;
	float velocita_iniziale;
	float velocita_iniziale_q;
	float fidelity_old=0.0;

	beta  = 0.0;
	alpha = self->LINE_SEARCH_INIT;

	buffers.set_w2zero(buffers.d_w, NULL );

	buffers.terminenoto=1;
	buffers.calculate_grad_error( buffers.d_dw,   buffers.d_w,self->peso_overlap, p4t) ;
	buffers.complete_gradient_l1(  buffers.d_w, buffers.d_df ,buffers.d_dw ,  weight );
	// nota che qui il grad e ' in realta una freccia che va nella buona direzione.
	// Per la parte L1 consideriamo che lo 0 est 0
	buffers.copia_dws( buffers.d_p , buffers.d_df);


	velocita_iniziale    =   cublasSdot(  pg2d.np*ncomps   , buffers.d_df , 1, buffers.d_p , 1)     ;
	velocita_iniziale_q  =   cublasSdot(  pg2d.np*ncomps   , buffers.d_dw , 1, buffers.d_p , 1)     ;

	for(int iter = 0 ; iter < self->ITERATIVE_CORRECTIONS ; iter++){

	  buffers.terminenoto=0;
	  buffers.calculate_grad_error( buffers.d_dp   , buffers.d_p ,self->peso_overlap, p4t) ;
	  k = (-1.0)*cublasSdot(  pg2d.np*ncomps   , buffers.d_p , 1, buffers.d_dp , 1);
	  if(k<=0.0f) k=       (1.0f/HUGE)/(  0x200000  ) ;
	  if(velocita_iniziale<0){
	    printf(" ERROR: not a descent direction \n");
	    // exit(1);
	  }
      
	  // printf("VALORE DI K %e   velocita_iniziale  %e velocita_iniziale_q  %e \n", k, velocita_iniziale, velocita_iniziale_q );
	  if(0) {

	    alpha = velocita_iniziale/k;
	  } else {
	    float a[3];
	    exit(0);
	    buffers.line_search_SG( velocita_iniziale,  velocita_iniziale_q     ,  buffers.d_p,
				    buffers.d_w, buffers.d_notzero, buffers.d_dw  ,   velocita_iniziale/k  ,
				    weight, pg2d.np*ncomps, k,log_fbdl, a,NULL);
	    alpha=a[1];
	
	    // printf(" ALPHA %e contro %e  \n", alpha, velocita_iniziale/k);
	    // exit(0);
	  }
	  cublasSaxpy(pg2d.np*ncomps ,alpha,buffers.d_p, 1,buffers.d_w, 1);

	  {
	    float fidelity ;
	    if(iter && iter%1==0) {
	      buffers.terminenoto=1;
	      fidelity = buffers.calculate_grad_error( buffers.d_dw,   buffers.d_w,self->peso_overlap, p4t) ;
	    } else {
	      cublasSaxpy(pg2d.np*ncomps ,alpha,buffers.d_dp, 1,buffers.d_dw, 1);
	      fidelity = fidelity_old - alpha*velocita_iniziale_q +  k*alpha*alpha/2   ;
	    }

	    fidelity_old = fidelity ;

	    float L1       =  buffers.complete_gradient_l1_andsum(  buffers.d_w, buffers.d_df ,buffers.d_dw ,  weight );
	    //  cublasSasum(    pg2d.np*ncomps , buffers.d_w, 1 )   ;

	    if(self->verbosity>0) {
	      if(iter%10==0) printf(" ITER %d TOTALE %20.14e fidelity %e L1 %e  sparsita... \n", iter,  fidelity+L1*weight, fidelity , L1 ); // , sparsita  );
	    }
	  }
	  CUDACHECK;
	  {
	    {
	      float A,B;
	      A =  cublasSdot( pg2d.np*ncomps , buffers.d_dp, 1 ,buffers.d_df  , 1); ;
	      B =  cublasSdot( pg2d.np*ncomps , buffers.d_dp, 1 , buffers.d_p  , 1); ;
	      beta = -A/B ;
	      // printf(" A B  BETA %e %e %e \n", A,B , beta);
	    }
	    if (beta<0) beta=0.0f ;

	    buffers.call_kern_dp_and_velocita(  beta , buffers.d_df,  buffers.d_df,buffers.d_dw,   buffers.d_p,
						velocita_iniziale, velocita_iniziale_q ) ;

	    // cublasSscal( pg2d.np*ncomps ,beta,buffers.d_p, 1  ) ;
	    // cublasSaxpy(pg2d.np*ncomps ,1.0f ,  buffers.d_df , 1,buffers.d_p, 1);
	    // velocita_iniziale_q  =   cublasSdot(  pg2d.np*ncomps   , buffers.d_dw , 1, buffers.d_p , 1)     ;
	    // velocita_iniziale  =   cublasSdot(  pg2d.np*ncomps   , buffers.d_df , 1, buffers.d_p , 1)     ;

	  }
	}
      }
      break;


    case 4: // Mirone
      {
	if(self->verbosity>0) {
	  printf("==============================  in fb_dl case 4  =================================\n");
	}

        double beta;
        double alpha;
        double k;
        float velocita_iniziale;
        float velocita_iniziale_q;

        beta  = 0.0;
        alpha = self->LINE_SEARCH_INIT;

        buffers.set_w2zero(buffers.d_w );

        buffers.terminenoto=1;
        buffers.calculate_grad_error( buffers.d_dw,   buffers.d_w,self->peso_overlap, p4t) ; //d_dw = quadratic part of gradient (wrt. w)
        buffers.complete_gradient_l1(  buffers.d_w, buffers.d_df ,buffers.d_dw ,  weight );  //d_df = d_dw + subgradient of L1, taking into account the direction where the quadratic part "pulls"
        // nota che qui il grad e ' in realta una freccia che va nella buona direzione.
        // Per la parte L1 consideriamo che lo 0 est 0

        buffers.copia_dws( buffers.d_p , buffers.d_df); //d_p <- d_df

        velocita_iniziale    =   cublasSdot(  pg2d.np*ncomps   , buffers.d_df , 1, buffers.d_p , 1)     ; //norm 2 of total gradient //??
        velocita_iniziale_q  =   cublasSdot(  pg2d.np*ncomps   , buffers.d_dw , 1, buffers.d_p , 1)     ; //d_dw*d_df                //??

        for(int iter = 0 ; iter < self->ITERATIVE_CORRECTIONS ; iter++){

          buffers.terminenoto=0;
          buffers.calculate_grad_error( buffers.d_dp   , buffers.d_p ,self->peso_overlap, p4t) ;    //"d_dp = P^T*P*P"
          k = (-1.0)*cublasSdot(  pg2d.np*ncomps   , buffers.d_p , 1, buffers.d_dp , 1);            //parabola slope
          if(k<=0.0f) k=       (1.0f/HUGE)/(  0x200000  ) ;
          if(velocita_iniziale<0){
            printf(" ERROR: not a descent direction \n");
            exit(1);
          }

          // printf("VALORE DI K %e   velocita_iniziale  %e velocita_iniziale_q  %e \n", k, velocita_iniziale, velocita_iniziale_q );

          {
            float a[3];
	    exit(0);

            buffers.line_search_SG( velocita_iniziale,  velocita_iniziale_q     ,  buffers.d_p,
                                    buffers.d_w, buffers.d_notzero, buffers.d_dw  ,   velocita_iniziale/k  ,
                                    weight, pg2d.np*ncomps, k,log_fbdl, a, NULL);
            alpha=a[1];

            // printf(" ALPHA %e contro %e  \n", alpha, velocita_iniziale/k);
            // exit(0);
          }

          call_kern_fill_invertiti_given_alpha( pg2d.np*ncomps,  buffers.d_invertiti, buffers.d_w, buffers.d_p, alpha) ; //?? what is that ?

          // printf(" ALPHA %e contro %e \n", alpha, velocita_iniziale/k);

          // aggiorna d_w
          cublasSaxpy(pg2d.np*ncomps ,alpha,buffers.d_p, 1,buffers.d_w, 1); //w = w + alpha*p : update "x"
          {
            // aggiorna d_dw
            cublasSaxpy(pg2d.np*ncomps ,alpha,buffers.d_dp, 1,buffers.d_dw, 1); //dw = dw + alpha*dp : update "residual"
          }


          CUDACHECK;

          if(iter%4==4) {
            call_kern_createapply_projector( pg2d.np*ncomps,  buffers.d_w, buffers.d_p, buffers.d_dw,
                                             buffers.d_invertiti, weight,
                                             buffers.d_dp,  buffers.d_dfprojected ,buffers.d_df,
                                             *Lipschitz*0  , 1);

            float L1       =  buffers.complete_gradient_l1_andsum(  buffers.d_w, buffers.d_df ,buffers.d_dw ,  weight );
            call_kern_createapply_projector( pg2d.np*ncomps,  buffers.d_w, buffers.d_p, buffers.d_dw,
                                             buffers.d_invertiti, weight,
                                             buffers.d_dp,  buffers.d_dfprojected ,buffers.d_df,
                                             *Lipschitz*0  , 2);

            {
              float A,B;
              A =  cublasSdot( pg2d.np*ncomps , buffers.d_dp, 1 ,buffers.d_dfprojected  , 1); ;
              B =  cublasSdot( pg2d.np*ncomps , buffers.d_dp, 1 , buffers.d_p  , 1); ;
              beta = -A/B ;
              // printf(" A B  BETA %e %e %e \n", A,B , beta);
            }
            if (beta<0) beta=0.0f ;

            cublasSscal( pg2d.np*ncomps ,beta,buffers.d_p, 1  ) ;
            cublasSaxpy(pg2d.np*ncomps ,1.0f ,  buffers.d_dfprojected  , 1,buffers.d_p, 1);

            velocita_iniziale_q=   cublasSdot(  pg2d.np*ncomps   , buffers.d_dw , 1, buffers.d_p , 1)     ;
            velocita_iniziale  =   cublasSdot(  pg2d.np*ncomps   , buffers.d_df , 1, buffers.d_p , 1)     ;

          } else {

            call_kern_createapply_projector( pg2d.np*ncomps,  buffers.d_w, buffers.d_p, buffers.d_dw,
                                             buffers.d_invertiti, weight,
                                             buffers.d_dp,  buffers.d_dfprojected ,buffers.d_df,
                                             *Lipschitz*0  , 1); //<-- Lipschitz*0 ...
            {
              buffers.terminenoto=1;
              float fidelity = buffers.calculate_grad_error( buffers.d_dw,   buffers.d_w,self->peso_overlap, p4t) ;
              float L1       =  buffers.complete_gradient_l1_andsum(  buffers.d_w, buffers.d_df ,buffers.d_dw ,  weight );
              //  cublasSasum(    pg2d.np*ncomps , buffers.d_w, 1 )   ;
              if( iter%10 ==0) printf(" ITER %d TOTALE %20.14e fidelity %e L1 %e  sparsita... \n", iter,  fidelity+L1*weight, fidelity , L1 ); // , sparsita  );
            }

            float scal_ddp_ddfprojected , scal_ddp_dp;
            buffers.call_kern_createapply_projector_coalesced( pg2d.np*ncomps, buffers.d_w, buffers.d_p, buffers.d_dw,
                                                               buffers.d_invertiti, weight,
                                                               buffers.d_dp,  buffers.d_dfprojected, buffers.d_df, *Lipschitz *0.0 ,
                                                               scal_ddp_ddfprojected , scal_ddp_dp
                                                               );
            beta = -scal_ddp_ddfprojected/scal_ddp_dp ;
            if (beta<0) beta=0.0f ;
            buffers.call_kern_dp_and_velocita(  beta , buffers.d_dfprojected,  buffers.d_df,buffers.d_dw,   buffers.d_p,
                                                velocita_iniziale, velocita_iniziale_q ) ;
          }

        }
      }
      break;


      
    case 5: // Conjugate Subgradient (07/04/15)
      {
	
	if(self->verbosity>0) {
	  printf("====================  in fb_dl case 5 (CONJUGATE SUB-GRADIENT) =======================\n");
	  printf("Np = %d , Ncomps = %d\n",pg2d.np, ncomps);
	  printf("Requiring at least %.2f MB\n",4*((7+5)*pg2d.np*ncomps + 2*(1+pg2d.np*ncomps/WKSIZE))/1e6);
	}
	
	//#define PROFILE
#ifdef PROFILE
        cudaEvent_t tstart, tstart_all, tstop;
        cudaEventCreate(&tstart);
        cudaEventCreate(&tstart_all);
        cudaEventCreate(&tstop);
        float elapsedTime;
#endif
        int size_w = pg2d.np*ncomps;
        int num_bins = self->num_bins;
	
//        int num_projs = self->nprojs_span;
	 
        float beta/*, alpha*/, k;
        double alpha;
        float velocita_iniziale, velocita_iniziale_q;
        beta  = 0.0;
        alpha = self->LINE_SEARCH_INIT;
        buffers.set_w2zero(buffers.d_w );
        buffers.set_w2zero(buffers.d_wold );
        buffers.set_w2zero(buffers.d_notzero ); //???
        buffers.set_w2zero(buffers.d_p);

	CUDACHECK ; 
	
        //--------------------------
        dim3 blk, grd, grdbis;
        //1D grid/blocks. Beware that for CC < 3.0, one should have nblocks < 65535
        // grd = dim3( iDivUp(size_w ,WKSIZE) , 1 , 1 );
	
        grd = dim3( min(  iDivUp(size_w, WKSIZE), 65535 ) , 1 , 1 );
        grdbis = dim3( min(  iDivUp(size_w, WKSIZE), 65535 ) , 1 , 1 );
        blk = dim3( WKSIZE , 1 , 1 );
	
        //int flag_restart;
	//        int restart_interval = 50; //TODO : this as a parameter
        // int CSG_DO_RESTART_IF_INCREASE = 1; //TODO : this as a parameter
	
        float shrink =  0.8; // 0.6;//0.9; //TODO : this as a parameter
        float increase= 0.01;  // 0.002;//0.02; //TODO : this as a parameter
        float beta_num, beta_denom;
	
        //Available arrays in buffers are d_w, d_dw, d_p, d_dp, d_invertiti, d_dfprojected, d_df
        float * mult, *p_old, *dp_old, *sticker, *w_dummy;
	CUDACHECK ; 
	
        CUDA_SAFE_CALL(cudaMalloc(&p_old,size_w*sizeof(float)));
        CUDA_SAFE_CALL(cudaMalloc(&dp_old,size_w*sizeof(float)));
        CUDA_SAFE_CALL(cudaMalloc(&sticker,size_w*sizeof(float))); //use buffers.d_notzero ?
        CUDA_SAFE_CALL(cudaMalloc(&w_dummy,size_w*sizeof(float)));
        CUDA_SAFE_CALL(cudaMalloc(&mult,size_w*sizeof(float)));

	CUDACHECK ; 

	
        cudaMemset(w_dummy, 0, size_w*sizeof(float));
        cudaMemset(mult, 0, size_w*sizeof(float));
        csg_kern_eltw_add_constant<<<grdbis,blk>>>(mult,1.0f,size_w);
        buffers.set_w2zero(p_old);
        buffers.set_w2zero(dp_old);

	CUDACHECK ; 

	
        //Rings
        float* rings = NULL, *drings = NULL, *minus_grad_tot_rings = NULL, *p_rings = NULL, *rings_old = NULL;
        float *dp_rings = NULL, *dp_rings_old = NULL, *p_rings_old = NULL, *mult_rings = NULL, *sticker_rings = NULL, *rings_dummy = NULL;
        if(self->ITER_RING_HEIGHT){
          //available HOST buffers are rings, drings, rings_old, rings_tmp. Do not use ringstmp ! (used in calculate_grad_error)
	  
          rings = buffers.rings;
          drings = buffers.drings;
          minus_grad_tot_rings = buffers.rings_tmp;
          rings_old = buffers.rings_old;
	  
          p_rings = (float*) malloc(buffers.multirings_size*sizeof(float));
          dp_rings = (float*) malloc(buffers.multirings_size*sizeof(float));
          dp_rings_old = (float*) malloc(buffers.multirings_size*sizeof(float));
          p_rings_old = (float*) malloc(buffers.multirings_size*sizeof(float));
          mult_rings = (float*) malloc(buffers.multirings_size*sizeof(float));
          sticker_rings = (float*) malloc(buffers.multirings_size*sizeof(float));
          rings_dummy = (float*) malloc(buffers.multirings_size*sizeof(float));
          memset(minus_grad_tot_rings, 0, buffers.multirings_size*sizeof(float));
          memset(p_rings, 0, buffers.multirings_size*sizeof(float));
          memset(dp_rings, 0, buffers.multirings_size*sizeof(float));
          memset(dp_rings_old, 0, buffers.multirings_size*sizeof(float));
          memset(p_rings_old, 0, buffers.multirings_size*sizeof(float));
          memset(rings_dummy, 0, buffers.multirings_size*sizeof(float));
          for (int i=0; i<buffers.multirings_size; i++) mult_rings[i] = 1;
        }
	
        float fid = 0.0f, L1_norm = 0.0f, sparsity = 1.0f, L1_norm_rings = 0;
        float* energy_vector = (float*) calloc(self->ITERATIVE_CORRECTIONS, sizeof(float));
        //--------------------------

	CUDACHECK ; 
	
        buffers.terminenoto=1;
        buffers.calculate_grad_error( buffers.d_dw,   buffers.d_w,self->peso_overlap, p4t, drings, rings) ; //d_dw = (minus) quadratic part of gradient (wrt. w)
	
        buffers.complete_gradient_l1(  buffers.d_w, buffers.d_df ,buffers.d_dw ,  weight, rings, drings, minus_grad_tot_rings );  //d_df = d_dw + subgradient of L1, taking into account the direction where the quadratic part "pulls"
        buffers.copia_dws( buffers.d_p , buffers.d_df, p_rings, minus_grad_tot_rings); //d_p <- d_df ... initial direction descent is the opposite of the total gradient (d_df is minus grad !)
        velocita_iniziale    =   buffers.DDcublasSdot(  pg2d.np*ncomps   , buffers.d_df , 1, buffers.d_p , 1)     ; //norm 2 of total gradient
        velocita_iniziale_q  =   buffers.DDcublasSdot(  pg2d.np*ncomps   , buffers.d_dw , 1, buffers.d_p , 1)     ; //d_dw*d_df
	CUDACHECK ; 

        if (rings != NULL) {
          float acc1 = 0, acc2 = 0;
          csg_dotp(minus_grad_tot_rings, p_rings, &acc1, buffers.multirings_size, drings, p_rings, &acc2);
	  if(self->verbosity>1) {
	    printf(" acc2 %e \n",  acc2  );
	  }
          velocita_iniziale += acc1;
          velocita_iniziale_q += acc2;
        }
	
        for (int iter = 0; iter < self->ITERATIVE_CORRECTIONS; iter++) {
#ifdef PROFILE
	  cudaEventRecord(tstart_all, 0);
#endif
	  
#ifdef PROFILE
	  cudaEventRecord(tstart, 0);
#endif
          buffers.terminenoto=0;
	  /*
	    buffers.calculate_grad_error( buffers.d_dp   , buffers.d_p ,self->peso_overlap, p4t, dp_rings, p_rings);     //"d_dp = P^T*P*p"
	    #ifdef PROFILE
	    cudaEventRecord(tstop, 0); cudaEventSynchronize(tstop); cudaEventElapsedTime(&elapsedTime, tstart, tstop);
	    printf("grad_dp took %lf ms\n",elapsedTime);
	    #endif
	  */

	  CUDACHECK ; 

	  csg_call_multiplier_fromp2mp_kernel(buffers.d_notzero, buffers.d_p, mult,  size_w);  // la direction, la vraie
          if (rings) {
	    for(int i=0;i<(buffers.multirings_size); i++)
	      {   p_rings[i] *=  mult_rings[i];  }           // la vraie
	  }
	  CUDACHECK ; 
		  
          buffers.calculate_grad_error( buffers.d_dp   , buffers.d_notzero ,self->peso_overlap, p4t, dp_rings, p_rings);     // la derive des vrais gradients par rapport a alpha de line search
	  // csg_call_multiplier_fromp2mp_kernel(buffers.d_notzero, buffers.d_p, mult,  size_w);  // la direction, la vraie
          // if (rings) {
	  csg_call_multiplier_fromp2mpinplace_kernel( buffers.d_dp, mult,  size_w);   // RRRRRRR
	  CUDACHECK ; 
	  
          k = (-1.0)*buffers.DDcublasSdot( pg2d.np*ncomps  , buffers.d_dp , 1, buffers.d_p , 1);            //parabola slope RRRRRRR
          if (rings) {
            float acc = 0;
            csg_dotp(p_rings, dp_rings, &acc, buffers.multirings_size);
            k -= acc;
            // printf("Kx_tot = %e \n",k);
	    for(int i=0; i<(buffers.multirings_size); i++) {
	      p_rings[i]  /=  mult_rings[i];
	      dp_rings[i] *=  mult_rings[i];
	    }  // on revient au referentiel adaptatif
          }
	  
	  CUDACHECK ; 
	  
          if(k<=0.0f) k=       (1.0f/HUGE)/(  0x200000  ) ;
          if(velocita_iniziale<0){
            printf(" ERROR (it %d): not a descent direction \n",iter);
	    // break;
	    csg_call_multiplier_fromp2mpinplace_kernel( buffers.d_w, mult,  size_w);   // RRRRRRR
	    cudaMemset(mult, 0, size_w*sizeof(float));
	    csg_kern_eltw_add_constant<<<grdbis,blk>>>(mult,1.0f,size_w);
	    buffers.terminenoto=1;
	    buffers.calculate_grad_error( buffers.d_dw,   buffers.d_w,self->peso_overlap, p4t, drings, rings) ; 
	    buffers.complete_gradient_l1(  buffers.d_w, buffers.d_df ,buffers.d_dw ,  weight, rings, drings, minus_grad_tot_rings );
	    buffers.copia_dws( buffers.d_p , buffers.d_df, p_rings, minus_grad_tot_rings);
	    velocita_iniziale    =   buffers.DDcublasSdot(  pg2d.np*ncomps   , buffers.d_df , 1, buffers.d_p , 1)     ; 
	    velocita_iniziale_q  =   buffers.DDcublasSdot(  pg2d.np*ncomps   , buffers.d_dw , 1, buffers.d_p , 1)     ; 
	    CUDACHECK ;

	    buffers.set_w2zero(p_old);
	    buffers.set_w2zero(dp_old);
	    
	    if (rings != NULL) {

	      for(int i=0;i<(buffers.multirings_size); i++)
		{
		  p_rings[i] *=  mult_rings[i];
		  mult_rings[i]=1;
		}           // la vraie
	      float acc1 = 0, acc2 = 0;
	      csg_dotp(minus_grad_tot_rings, p_rings, &acc1, buffers.multirings_size, drings, p_rings, &acc2);
	      if(self->verbosity>1) {
		printf(" acc2 %e \n",  acc2  );
	      }
	      velocita_iniziale += acc1;
	      velocita_iniziale_q += acc2;
	      memset(dp_rings_old, 0, buffers.multirings_size*sizeof(float));
	      memset(p_rings_old, 0, buffers.multirings_size*sizeof(float));
	    }
	    continue ;
            // exit(1);
          }
	  
#ifdef PROFILE
	  cudaEventRecord(tstart, 0);
#endif
          { //line search -> alpha
            float a[3];
            velocita_iniziale_q=   buffers.DDcublasSdot(  pg2d.np*ncomps   , buffers.d_dw , 1, buffers.d_p , 1)     ; // gradients et direction
            velocita_iniziale  =   buffers.DDcublasSdot(  pg2d.np*ncomps   , buffers.d_df , 1, buffers.d_p , 1)     ; // dans le ref. adapt.
	    CUDACHECK ; 

            if (rings != NULL) {
              float acc1 = 0, acc2 = 0;
              csg_dotp(minus_grad_tot_rings, p_rings, &acc1, buffers.multirings_size, drings, p_rings, &acc2);
              velocita_iniziale += acc1;
              velocita_iniziale_q += acc2;
	    }
	    CUDACHECK ; 
	    
            buffers.line_search_SG( velocita_iniziale,  velocita_iniziale_q     ,  buffers.d_p,
                                    buffers.d_w, buffers.d_notzero, buffers.d_dw  ,   velocita_iniziale/k  ,
                                    weight, pg2d.np*ncomps, k,log_fbdl, a, mult,
                                    rings, p_rings, mult_rings, weight_rings);
	    CUDACHECK ; 

            alpha=a[1];
          }
	  // printf(" ---\n");
          if (alpha != alpha) { fprintf(stderr,"[%d] alpha is NaN !\n",iter); exit(-1); }
	  CUDACHECK ; 
	  
#ifdef PROFILE
	  cudaEventRecord(tstop, 0); cudaEventSynchronize(tstop); cudaEventElapsedTime(&elapsedTime, tstart, tstop);
	  printf("line search took %lf ms\n",elapsedTime);
#endif
	  CUDACHECK ; 

          //update variables
          csg_kern_update_variables<<<grd,blk>>>(mult, buffers.d_w, buffers.d_wold, buffers.d_p, p_old, buffers.d_dp, dp_old, sticker, w_dummy, buffers.d_dw, alpha, weight, shrink, increase, iter, size_w);
	  CUDACHECK ; 
	  
          if (rings)
	    csg_update_rings(rings, rings_old, drings, p_rings, p_rings_old, dp_rings, dp_rings_old, mult_rings, sticker_rings, rings_dummy, alpha, weight_rings, shrink, increase, iter, buffers.multirings_size);
	  
          buffers.terminenoto=1;
	  CUDACHECK ; 

	  if(iter%10 ==0 || iter == self->ITERATIVE_CORRECTIONS-1 ) {
	    fid = buffers.calculate_grad_error(buffers.d_dw, w_dummy, self->peso_overlap, p4t, drings, rings_dummy);
	  }
          buffers.complete_gradient_l1(w_dummy, buffers.d_df, buffers.d_dw, weight, rings_dummy, drings, minus_grad_tot_rings);
	  
	  csg_call_multiplier_kernel(buffers.d_df, buffers.d_dw, mult, sticker, size_w);
	  if (rings) csg_mult(minus_grad_tot_rings, drings, mult_rings, sticker_rings, buffers.multirings_size);
	  
          L1_norm = cublasSasum(size_w, w_dummy, 1);
          // sparsity = cublasSasum(size_w, mult, 1)/size_w;
          sparsity = cublasSasum(size_w, sticker, 1)/size_w;
          if (rings) {
            L1_norm_rings = 0;
            for (int i=0; i<buffers.multirings_size; i++) L1_norm_rings += fabsf(rings_dummy[i]); //fabsf(rings[i]);
          }
	  
#ifdef PROFILE
	  cudaEventRecord(tstop, 0); cudaEventSynchronize(tstop); cudaEventElapsedTime(&elapsedTime, tstart, tstop);
	  printf("sasum() (x2) took %lf ms\n",elapsedTime);
#endif
	  
          //update beta (Polak-Ribiere)
#ifdef PROFILE
	  cudaEventRecord(tstart, 0);
#endif
          beta_num = buffers.DDcublasSdot(size_w, dp_old, 1, buffers.d_df, 1); //d_df is -grad_tot ...
          beta_denom = buffers.DDcublasSdot(size_w, dp_old, 1, p_old, 1);
          if (rings) {
            float acc1 = 0, acc2 = 0;
            csg_dotp(dp_rings_old, minus_grad_tot_rings, &acc1,buffers. multirings_size, dp_rings_old, p_rings_old, &acc2);
            beta_num += acc1;
            beta_denom += acc2;
          }
          beta = -beta_num/beta_denom;
          //if (iter %1 == 0) printf(">> beta = %e \t alpha = %e\n",beta,alpha); //DEBUG
          if (beta < 0) beta = 0.0f;
	  
          //update conjugate direction : p = -grad + beta*p_old
          cudaMemcpy(buffers.d_p, buffers.d_df, size_w*sizeof(float),cudaMemcpyDeviceToDevice);//d_df is -grad_tot...
          if (beta > 0) cublasSaxpy(size_w, beta, p_old, 1, buffers.d_p, 1);
          if (rings) {
            for (int i=0; i<buffers.multirings_size; i++) {
              p_rings[i] = minus_grad_tot_rings[i] +beta*p_rings_old[i];
            }
          }
	  
#ifdef PROFILE
	  cudaEventRecord(tstop, 0); cudaEventSynchronize(tstop); cudaEventElapsedTime(&elapsedTime, tstart, tstop);
	  printf("update took %lf ms\n",elapsedTime);
#endif
	  
          energy_vector[iter] = fid + weight*L1_norm + weight_rings*L1_norm_rings;
          // if (iter > 1 && CSG_DO_RESTART_IF_INCREASE && energy_vector[iter] > energy_vector[iter-1]) flag_restart = 1;
	  
	  if(self->verbosity>0) {
	    if (iter %10 == 0) printf("[CSG] It %d \t Energy %e \t Fidelity %e \t L1 %e\t Sparsity %.3f%% \t Rings %e\n",iter,fid+weight*L1_norm+weight_rings*L1_norm_rings,fid,L1_norm,sparsity*100,L1_norm_rings);
	  }
          //if (iter%10 == 0) qdplot(rings, num_bins);
	  
	  
          //DEBUG ----
	  if (((CCspace *) (self->void_ccspace_ptr))->params.do_dump_hrings)
	    {
	      char nome[10000];
	      
	      sprintf(nome,"rings_%04d.dat", p4t.ctxstruct->num_slice );
	      
	      FILE* filedebug = fopen(nome ,"w");

	      int wrote = fwrite(rings  ,doppio*p4t.ctxstruct->NUMBER_OF_RINGS * num_bins*sizeof(float),1,filedebug);
	      fclose(filedebug);
	    }
          //----
	  
	  
#ifdef PROFILE
	  cudaEventRecord(tstop, 0); cudaEventSynchronize(tstop); cudaEventElapsedTime(&elapsedTime, tstart_all, tstop);
	  printf("iteration took %lf ms\n",elapsedTime);
	  puts("------");
#endif
	  
        } //end of iterations
	
        FILE* flid = fopen("energy_csg.dat", "wb");
        fwrite(energy_vector, sizeof(float), self->ITERATIVE_CORRECTIONS, flid);
        fclose(flid);
	
	
	//  flid = fopen("beta_vector.dat", "w  b");
	//  fwrite(alpha_vector, sizeof(float), self->ITERATIVE_CORRECTIONS, flid);
	//  fclose(flid);
	
        free(energy_vector);
        cudaFree(p_old);
        cudaFree(dp_old);
        cudaFree(sticker);
        cudaFree(w_dummy);
        cudaFree(mult);
      }
      break;
      
      //END OF ALL OPTIMIZATION ALGORITHMS CASES
    }
  
  cudaMemcpy( SLICE, buffers.d_image , pg2d.pgy.dim*pg2d.pgx.dim*pg2d.doppio*sizeof(float),  cudaMemcpyDeviceToHost );

}



#define CONO_GPU_MULT  4
#define PROCONO_GPU_MULT  2

// __global__ static void    pro_gputomo_conicity_kernel(float *d_SINO, // da allocare
//                            int nprojs_span,
//                            int nslices_data,
//                            int num_bins,
//                            float axis_position,
//                            float cpu_offset_x,
//                            float cpu_offset_y ,
//                            float *d_cos_s ,
//                            float *d_sin_s ,
//                            float *d_axis_s,
//                            int  Nfirstslice,
//                            int  nslices,
//                            int num_x,
//                            int num_y,
//                            int  data_start,
//                            float source_distance,
//                            float detector_distance,
//                            float v2x,
//                            float v2z,
//                            float v_size,
//                            float SOURCE_X,
//                            float SOURCE_Z) {

//   float cos_angle, sin_angle;
//   //  l'asse e' dato a partire dall' angolo  primo pixel, ma i centri dei pixel sono nel mezzo
//   // float acorr05;
//   // float Dmagni,d_px_ix,d_px_iy,d_pz_ix,d_pz_iy;

//   int iz, ix, projection;
//   ix  =  (blockDim.x * blockIdx.x + threadIdx.x) ;  // num_bins
//   projection  =  (blockDim.y * blockIdx.y + threadIdx.y)  ; // num_proj
//   iz  =  (blockDim.z * blockIdx.z + threadIdx.z + data_start) ;                   // nslices_data

//   // float magni, px, pz;

//   float magnicenter =  (source_distance+detector_distance)/ (source_distance )  ;
//   float axis_position_v = (axis_position-SOURCE_X)/(magnicenter*v2x) ;
//   cpu_offset_x -=  SOURCE_X+axis_position_v;
//   cpu_offset_y -=  SOURCE_X+axis_position_v;

//   cos_angle = d_cos_s[projection];
//   sin_angle = d_sin_s[projection];

//   float Dnumex = (source_distance+detector_distance)*v2x;
//   float Dnumez = (source_distance+detector_distance)*v2z;

//   float Xcorner = (d_axis_s[projection]-SOURCE_X)/(magnicenter*v2x)  +
//     (   cpu_offset_x *cos_angle    -     cpu_offset_y * sin_angle ) ;
//   float Ycorner =  axis_position_v +
//     (   cpu_offset_x *sin_angle    +     cpu_offset_y * cos_angle ) ;

//   float Ddeno0 = source_distance +     Ycorner *1.0e-6*v_size;
//   float Ddeno ;

//   float res = 0 ;
//   float Area;

//   int ivx, ivy;
//   float F, fvx, fvy, fvz;
//   res=0.0f;

//   if(fabs(sin_angle)>fabs(cos_angle)) {
//     Area = 1.0/ fabs(sin_angle);
//     for(ivx=0; ivx<num_x; ivx++) {
//       Ddeno = Ddeno0    +   ivx*sin_angle * v_size *1.0e-6;
//       F  = (sin_angle-(SOURCE_X-ix)*cos_angle*v_size*1.0e-6/Dnumex);
//       fvy   =( ( Xcorner+ivx*cos_angle ) + (SOURCE_X-ix)* (Ddeno)/Dnumex  );
//       fvy = fvy / F ;
//       Ddeno +=   fvy*cos_angle * v_size *1.0e-6;
//       fvz  = (iz-SOURCE_Z)*  Ddeno    / Dnumez  -Nfirstslice;
//       if( fvy>0 && fvy< num_y ) {
//  res +=  tex3D(texfor3D,  ivx  + 0.5f  , fvy + 0.5f  , fvz+ 1.5f  );
//       }
//     }
//   } else {
//     Area = 1.0/ fabs(cos_angle);
//     for(ivy=0; ivy<num_y; ivy++) {
//       Ddeno = Ddeno0    +   ivy*cos_angle * v_size *1.0e-6;
//       F  = (cos_angle+(SOURCE_X-ix)*sin_angle*v_size*1.0e-6/Dnumex);
//       fvx   =( -( Xcorner-ivy*sin_angle ) + (ix-SOURCE_X)*  (Ddeno)/Dnumex );
//       fvx = fvx / F ;
//       Ddeno +=   fvx*sin_angle * v_size *1.0e-6;
//       fvz  = (iz-SOURCE_Z)*Ddeno    / Dnumez -Nfirstslice;
//       if( fvx>0 && fvx< num_x ) {
//  res  +=  tex3D(texfor3D,  fvx  + 0.5f  , ivy + 0.5f  , fvz + 1.5f  );
//       }
//     }
//   }
//   iz -= data_start;
//   if((ix)<num_bins && projection<nprojs_span && (iz) < nslices_data) {
//     d_SINO[ ( (iz) *nprojs_span   +  projection)*num_bins  +ix]=res*Area;
//   }
// }


__global__ static void    multi_pro_gputomo_conicity_kernel(float *d_SINO, // da allocare
							    int nprojs_span,
							    int nslices_data,
							    int num_bins,
							    float axis_position,
							    float cpu_offset_x,
							    float cpu_offset_y ,
							    float *d_cos_s ,
							    float *d_sin_s ,
							    float *d_axis_s,
							    int  Nfirstslice,
							    int  nslices,
							    int num_x,
							    int num_y,
							    int  data_start,
							    float source_distance,
							    float detector_distance,
							    float v2x,
							    float v2z,
							    float v_size,
							    float SOURCE_X,
							    float SOURCE_Z,
							    DectTilt  dectTilt
							    ) {


  
  
  float cos_angle, sin_angle;
  //  l'asse e' dato a partire dall' angolo  primo pixel, ma i centri dei pixel sono nel mezzo
  // float acorr05;
  // float Dmagni,d_px_ix,d_px_iy,d_pz_ix,d_pz_iy;

  int iz, ix, projection;
  ix  =  (blockDim.x * blockIdx.x + threadIdx.x)* PROCONO_GPU_MULT ;  // num_bins
  projection  =  (blockDim.y * blockIdx.y + threadIdx.y)  ; // num_proj
  iz  =  (blockDim.z * blockIdx.z + threadIdx.z )* PROCONO_GPU_MULT  + data_start;                   // nslices_data


  float fix, fiz;
  if(dectTilt.sin_psi!=0.0f) {
    fix = SOURCE_X +  dectTilt.cos_psi*(ix-SOURCE_X)   - dectTilt.sin_psi * (iz-SOURCE_Z) ; 
    fiz = SOURCE_Z +  dectTilt.sin_psi*(ix-SOURCE_X)   + dectTilt.cos_psi * (iz-SOURCE_Z) ; 
  } else {
    fix = ix;
    fiz = iz;
  }
  if(dectTilt.sin_tilt!=0.0f) {
    float alpha =  ( 1.0e-6 * v_size/v2x) / ( source_distance+detector_distance) ;
    // intanto mettere in swap una variabile R inizializzata a fiz prima di cambiare fiz
    // Secondo il coseno deve moltiplicare da fuori della parentesi   
    float R = fiz;
    fiz = SOURCE_Z +  (  -dectTilt.sin_tilt*  (fiz-SOURCE_Z)*alpha  + 1.0f              ) * (fiz-SOURCE_Z) *dectTilt.cos_tilt ;
    fix = SOURCE_X +  (  -dectTilt.sin_tilt*  (R  -SOURCE_Z)*alpha  + 1.0f              ) * (fix-SOURCE_X) ;
  } else {
  }
  
  
  // float magni, px, pz;

  if(projection<=nprojs_span) {
  
    // float magni, px, pz;

    float magnicenter =  (source_distance+detector_distance)/ (source_distance )  ;
    float axis_position_v = (axis_position-SOURCE_X)/(magnicenter*v2x) ;
    // cpu_offset_x -=  SOURCE_X+axis_position_v;
    // cpu_offset_y -=  SOURCE_X+axis_position_v;
    cpu_offset_x -=  axis_position;
    cpu_offset_y -=  axis_position;

    cos_angle = d_cos_s[projection];
    sin_angle = d_sin_s[projection];

    float Dnumex = (source_distance+detector_distance)*v2x;
    float Dnumez = (source_distance+detector_distance)*v2z;

    float Xcorner = (d_axis_s[projection]-SOURCE_X)/(magnicenter*v2x)  +
      (   cpu_offset_x *cos_angle    -     cpu_offset_y * sin_angle ) ;
  
  float Ycorner =  axis_position_v*0 +
    (   cpu_offset_x *sin_angle    +     cpu_offset_y * cos_angle ) ;

  float Ddeno0 = source_distance +     Ycorner *1.0e-6*v_size;
  float Ddeno ;

  float Area;
  // float sum=0.0f;
  // int ipos;
  int ivx, ivy;
  float F, fvx, fvy, fvz;
  float  fvxb, fvyb, fvzb;
  float res[PROCONO_GPU_MULT*PROCONO_GPU_MULT];
  int imult, jmult;
  float d_z_z ;
  float d_F_x ;
  float md_fvyF_x;
  float d_fvy_x;
  float d_fvxF_x;
  float d_fvx_x;
  float d_z_x;
  {
    for(imult=0; imult<PROCONO_GPU_MULT*PROCONO_GPU_MULT ; imult ++)     res[imult]= 0.0f;
  }
  float Area_fz=1.0f ;
  {
    float L  =  (source_distance+detector_distance)/(1.0e-6);
    float H  =  (fix-SOURCE_X) * v_size/v2x;
    float V  =  (fiz-SOURCE_Z)* v_size/v2x;
    Area_fz  =  sqrt( (L*L+H*H+V*V)/(L*L+H*H) )  ;
  }


  
    if(fabs(sin_angle)>fabs(cos_angle)) {
      Area = 1.0/ fabs(sin_angle)*Area_fz;
 
    for(ivx=0; ivx<num_x; ivx++) {
      Ddeno = Ddeno0    +   ivx*sin_angle * v_size *1.0e-6;
      d_F_x =  cos_angle*v_size*1.0e-6/Dnumex;
      F  = (sin_angle-(SOURCE_X-fix)*d_F_x);
      md_fvyF_x =  (Ddeno)/Dnumex  ;
      fvy   =( ( Xcorner+ivx*cos_angle ) + (SOURCE_X-fix)*md_fvyF_x   );
      fvy = fvy / F ;
      d_fvy_x =  (-md_fvyF_x-fvy*d_F_x) /F ;
      Ddeno +=   fvy*cos_angle * v_size *1.0e-6;
      d_z_z = Ddeno    / Dnumez;
      d_z_x =  (fiz-SOURCE_Z)*d_fvy_x*cos_angle* v_size *1.0e-6 / Dnumez;
      fvz  = (fiz-SOURCE_Z)*d_z_z   -Nfirstslice;
      for(jmult=0; jmult < PROCONO_GPU_MULT; jmult++) {
	fvyb = fvy + jmult * d_fvy_x   ;
	for(imult=0; imult < PROCONO_GPU_MULT; imult++) {
	  fvzb = fvz + imult*d_z_z + jmult * d_z_x   ;
	  // if( fvyb>-0.01 && fvyb< num_y+0.01 )
	  {
	    res[ imult*PROCONO_GPU_MULT + jmult    ]  +=  tex3D(texfor3D,  ivx  + 0.5f  , fvyb + 0.5f  , fvzb+ 1.5f  ); // 0.5+1 , 1 is for the margin
	  }
	}
      }
    }
  } else {
    Area = 1.0/ fabs(cos_angle)*Area_fz;
    for(ivy=0; ivy<num_y; ivy++) {
      Ddeno = Ddeno0    +   ivy*cos_angle * v_size *1.0e-6;
      d_F_x =  -sin_angle*v_size*1.0e-6/Dnumex;
      F  = (cos_angle+(SOURCE_X-fix)*(-d_F_x));
      d_fvxF_x =  (Ddeno)/Dnumex  ;
      fvx   =( -( Xcorner-ivy*sin_angle ) + (fix-SOURCE_X)*d_fvxF_x  );
      fvx = fvx / F ;
      d_fvx_x =  ( d_fvxF_x-fvx*d_F_x) /F ;
      Ddeno +=   fvx*sin_angle * v_size *1.0e-6;
      d_z_z = Ddeno    / Dnumez;
      d_z_x =  (fiz-SOURCE_Z)*d_fvx_x*sin_angle* v_size *1.0e-6 / Dnumez;
      fvz  = (fiz-SOURCE_Z)*d_z_z -Nfirstslice;
      for(jmult=0; jmult < PROCONO_GPU_MULT; jmult++) {
	fvxb = fvx + jmult * d_fvx_x   ;
	for(imult=0; imult < PROCONO_GPU_MULT; imult++) {
	  fvzb = fvz + imult*d_z_z + jmult * d_z_x   ;
	  // if( fvxb>-0.01 && fvxb< num_x+0.01 )  // il passaggio a zero deve essere assicurato dalle slice zeros che fanno sandwich e che ho aggiunto
	    {
	      res[ imult*PROCONO_GPU_MULT + jmult    ]  +=  tex3D(texfor3D,  fvxb  + 0.5f  , ivy + 0.5f  , fvzb + 1.5f  );
	    }
	  }
	}
      }
    }
    iz -= data_start;
    for(jmult=0; jmult < PROCONO_GPU_MULT; jmult++) {
      for(imult=0; imult < PROCONO_GPU_MULT; imult++) {
	if((ix+jmult)<num_bins && projection<nprojs_span && (iz+imult) < nslices_data) {
	  d_SINO[ ( (iz+imult) *nprojs_span   +  projection)*num_bins  +ix+jmult ]=res[ imult*PROCONO_GPU_MULT + jmult    ]*Area*M_PI*0.5/nprojs_span;

	}
      }
    }
  }
}

__global__ static void    gputomo_conicity_kernel(float *d_SLICE, // da allocare
						  int num_x,
						  int num_y,
						  int first_proj,
						  int nprojs_span,
						  int num_bins,
						  float axis_position,
						  float gpu_offset_x,
						  float gpu_offset_y ,
						  float *d_cos_s ,
						  float *d_sin_s ,
						  float *d_axis_s,
						  int  Nfirstslice,
						  int  nslices,
						  int  data_start,
						  int  nslices_data,
						  float source_distance,
						  float detector_distance,
						  float v2x,
						  float v2z,
						  float voxel_size,
						  float SOURCE_X,
						  float SOURCE_Z,
						  DectTilt  dectTilt) {



  __shared__  float   shared[768] ;
  float  * sh_sin  = shared;
  float  * sh_cos  = shared+256;
  float  * sh_axis = sh_cos+256;

  float pcos, psin;
  //  l'asse e' dato a partire dall' angolo  primo pixel, ma i centri dei pixel sono nel mezzo
  float acorr05;
  float Dmagni,d_px_ix,d_px_iy,d_pz_ix,d_pz_iy;
  float Z ;

  int vzf, vxf, vyf;
  vxf  =  (blockDim.x * blockIdx.x + threadIdx.x)* CONO_GPU_MULT ;
  vyf  =  (blockDim.y * blockIdx.y + threadIdx.y)* CONO_GPU_MULT   ;
  vzf  =  blockDim.z * blockIdx.z + threadIdx.z  ;

  float vz, vx, vy;
  float magni, px, pz;
 
  float magnicenter =  (source_distance+detector_distance)/ (source_distance )  ;
  // float axis_position_v = (axis_position-SOURCE_X)/(magnicenter*v2x) ;
  
  // gpu_offset_x -=  SOURCE_X;
  // gpu_offset_y -=  SOURCE_X;
  
  gpu_offset_x -=  axis_position ;
  gpu_offset_y -=  axis_position ;

  {
    float res[ CONO_GPU_MULT* CONO_GPU_MULT ] ;
    for(int i=0; i< CONO_GPU_MULT* CONO_GPU_MULT ; i++) res[i]=0.0f;
    int lettofino=0;  // letto= read   fino=till

    vz =  vzf + Nfirstslice ;

    for(int proje=0; proje<nprojs_span; proje++) {
      if(0) {
	if(proje>=lettofino) {
	  __syncthreads();
	  int ip = (threadIdx.z*blockDim.y+threadIdx.y)*blockDim.x    + threadIdx.x  ;
	  if( ip< 256 && lettofino+ip < nprojs_span) {
	    sh_cos [ip] = d_cos_s[lettofino+ip  +first_proj] ; // populating the shared memory
	    sh_sin [ip] = d_sin_s[lettofino+ip  +first_proj] ;
	    sh_axis[ip] = d_axis_s[lettofino+ip +first_proj] ;
	  }
	  lettofino=lettofino+256; // 256=16*16 block size
	  __syncthreads();
	}
	pcos = sh_cos[ -(lettofino-256)+ proje] ;
	psin = sh_sin[-(lettofino-256)+ proje] ;

	acorr05=   sh_axis[-(lettofino-256) +proje] ;
      } else {
	pcos   =  d_cos_s[proje  +first_proj];
	psin   =  d_sin_s[proje  +first_proj];
	acorr05=  d_axis_s[proje +first_proj];
      }

      vx = (acorr05-SOURCE_X)/(magnicenter*v2x)+(gpu_offset_x + vxf)*pcos - (gpu_offset_y + vyf)*psin ;
      vy = ( gpu_offset_x + vxf ) * psin     +  ( gpu_offset_y + vyf )* pcos;

      magni =  (source_distance+detector_distance)/ (source_distance +   vy* voxel_size*1.0e-6 )  ;

      px = SOURCE_X +  magni*(vx) *v2x ;
      pz = (SOURCE_Z +  magni*vz *v2z) - data_start ;

      if(CONO_GPU_MULT>1 )  {
	Dmagni = -magni/ (source_distance +   vy* voxel_size*1.0e-6 )  * voxel_size*1.0e-6;
	d_px_ix=   (magni*pcos   +  Dmagni*vx  *psin)*v2x           ;
	d_px_iy=   (-magni*psin   +  Dmagni*vx  *pcos) *v2x         ;
	d_pz_ix=   Dmagni*vz *v2z *psin             ;
	d_pz_iy=   Dmagni*vz *v2z  *pcos            ;




	for(int iy=0; iy< CONO_GPU_MULT ; iy++) {

	  for(int ix=0; ix< CONO_GPU_MULT ; ix++) {

	    Z =  pz    + ix*d_pz_ix  + iy*d_pz_iy ;
	    if(Z<-0.5f) {
	      continue;
	      // Z=0;
	    }
	    if(Z>nslices_data-0.5f) {
	      continue;
	      // Z = nslices_data-1;
	    }

	    float X = px +   ix*d_px_ix    + iy* d_px_iy  ; 
	    
	    float X1,Z1;
	    if(dectTilt.sin_tilt!=0.0f) {

	    // vedi correzioni da fare in proieione e applica anche qui

	      float alpha =  ( 1.0e-6 * voxel_size/v2x) / ( source_distance+detector_distance) ;
	      Z1 = SOURCE_Z +  (  +dectTilt.sin_tilt*  (Z-SOURCE_Z)*alpha  + 1.0f    ) * (Z  - SOURCE_Z) /dectTilt.cos_tilt ;
	      X1 = SOURCE_X +  (  +dectTilt.sin_tilt*  (Z-SOURCE_Z)*alpha  + 1.0f    ) * (X  - SOURCE_X) ;
	    } else {
	      X1 = X ;
	      Z1 = Z;
	    }
	    
	    float fix, fiz;
	    if(dectTilt.sin_psi!=0.0f) {
	      fix = SOURCE_X +  dectTilt.cos_psi*(X1-SOURCE_X)   + dectTilt.sin_psi * ( Z1 + data_start - SOURCE_Z )              ; 
	      fiz = SOURCE_Z -  dectTilt.sin_psi*(X1-SOURCE_X)   + dectTilt.cos_psi * ( Z1 + data_start - SOURCE_Z ) - data_start ; 
	    } else {
	      fix =  X1;
	      fiz =  Z1;
	    }
	    
	    res[iy* CONO_GPU_MULT   +ix] += tex2D(texProjes,
						  fix +  0.5f   ,
						  fiz +  proje*nslices_data  +0.5f
						  );
	  }
	}
      } else {
	Z =  pz     ;
	if(Z<-0.5f) {
	  continue;
	  // Z=0;
	}
	if(Z>nslices_data-0.5f) {
	  continue;
	  // Z = nslices_data-1;
	}
	float X = px; 
	float X1,Z1;
	if(dectTilt.sin_tilt!=0.0f) {
	  float alpha =  ( 1.0e-6 * voxel_size/v2x) / ( source_distance+detector_distance) ;
	  Z1 = SOURCE_Z +  (  +dectTilt.sin_tilt*  (Z-SOURCE_Z)*alpha  + 1.0f/dectTilt.cos_tilt ) * (Z  - SOURCE_Z) ;
	  X1 = SOURCE_X +  (  +dectTilt.sin_tilt*  (Z-SOURCE_Z)*alpha  + 1.0f                   ) * (X  - SOURCE_Z) ;
	} else {
	  X1 = X ;
	  Z1 = Z;
	}
	
	float fix, fiz;
	if(dectTilt.sin_psi!=0.0f) {
	  fix = SOURCE_X +  dectTilt.cos_psi*(X1-SOURCE_X)   + dectTilt.sin_psi * ( Z1 + data_start - SOURCE_Z )              ; 
	  fiz = SOURCE_Z -  dectTilt.sin_psi*(X1-SOURCE_X)   + dectTilt.cos_psi * ( Z1 + data_start - SOURCE_Z ) - data_start ; 
	} else {
	  fix = X1;
	  fiz = Z1;
	}
	res[0] += tex2D(texProjes,  fix +  0.5f     , fiz +  proje*nslices_data  +  0.5f ) ;  ;
      }
    }
    if(CONO_GPU_MULT>1 )  {
      for(int iy=0; iy< CONO_GPU_MULT ; iy++) {
	for(int ix=0; ix< CONO_GPU_MULT ; ix++) {
	  if(vzf<nslices && vyf+iy<num_y && vxf+ix < num_x)     d_SLICE[     (vzf* num_y +vyf +iy)*num_x  + vxf +ix] += res[iy* CONO_GPU_MULT   +ix ];
	}
      }
    } else {
      if(vzf<nslices && vyf<num_y && vxf < num_x)     d_SLICE[     (vzf* num_y +vyf )*num_x  + vxf ] += res[0];
    }
  }
}
int       gpu_main_conicity(Gpu_Context * self,  float * SLICE, float *WORK_perproje ,
			    int Nfirstslice, int nslices, int data_start, int nslices_data,
			    float source_distance, float detector_distance,
			    float v2x ,   float v2z,float voxel_size,
			    float SOURCE_X , float SOURCE_Z ){
  cuCtxSetCurrent ( *((CUcontext *) self->gpuctx  ))  ;
  cudaArray * a_Proje_voidptr ;

  int pezzo_z=8, pezzo_x=8, pezzo_y=8;
  if(nslices<8) {
    pezzo_z=4, pezzo_x=8, pezzo_y=8;
  }
  if(nslices<4) {
    pezzo_z=2, pezzo_x=16, pezzo_y=16;
  }
  if(nslices<2) {
    pezzo_z=1, pezzo_x=16, pezzo_y=16;
  }
  int nx,ny,nz;
  nx = ( (int ) (self->num_x*1.0/(pezzo_x * CONO_GPU_MULT)  +0.999999));
  ny = ( (int ) (self->num_y*1.0/(pezzo_y* CONO_GPU_MULT) +0.999999));
  nz = ( (int ) (nslices*1.0/pezzo_z +0.999999));

  float *d_SLICE;
  CUDA_SAFE_CALL(cudaMalloc(  &(d_SLICE) , sizeof(float) * nslices*self->dimrecx* self->dimrecy      ));
  CUDA_SAFE_CALL(cudaMemset( d_SLICE,0, sizeof(float) *nslices* self->dimrecx* self->dimrecy    ));

  int npjs_step= self->nprojs_span ;
  while(npjs_step*nslices_data > 65530)   npjs_step--;

  if(self->verbosity>0) {
    printf(" allocation  self->num_bins    , self->nprojs_span, nslices_data  %d %d %d \n",self->num_bins,self->nprojs_span,nslices_data ) ;
  }

  CUDA_SAFE_CALL( cudaMallocArray( &(a_Proje_voidptr), &floatTex ,  self->num_bins    , npjs_step*nslices_data) );

  int first_proj = 0;
  for(first_proj = 0;first_proj<self->nprojs_span;first_proj+= npjs_step) {

    int mynproj = min(npjs_step,self->nprojs_span-first_proj);

    CUDA_SAFE_CALL( cudaMemcpyToArray( a_Proje_voidptr, 0, 0,WORK_perproje+first_proj*self->num_bins*nslices_data,
				       mynproj*self->num_bins*sizeof(float) *nslices_data,
				       cudaMemcpyHostToDevice) );

    texProjes.filterMode = cudaFilterModeLinear;
    texProjes.addressMode[1] = cudaAddressModeClamp;
    texProjes.addressMode[0] = cudaAddressModeBorder;
    
    texProjes.normalized = false;

    CUDA_SAFE_CALL( cudaBindTextureToArray(texProjes,a_Proje_voidptr) );
    
    DectTilt dectTilt = {  sin(self->tilt_psi), cos(self->tilt_psi), sin(self->tilt_tilt), cos(self->tilt_tilt)  } ; 

    
    {
      dim3 dimGrid ( nx ,  ny, nz  );
      dim3      dimBlock(  pezzo_x, pezzo_y, pezzo_z  );
      if(self->verbosity>0) {
        printf(" RECONSTRUCTING CONSIDERING %d PROJECTION from %d  source_distance ,  SOURCE_X ,  v2x  %e %e %e\n",  mynproj, first_proj ,  source_distance ,  SOURCE_X ,  v2x );
      }
      gputomo_conicity_kernel<<<dimGrid,dimBlock,256*3*sizeof(float)>>> ( d_SLICE, // da allocare
									  self->num_x,
									  self->num_y,
									  first_proj,
									  mynproj,
									  self->num_bins,
									  self->axis_position,
									  self->gpu_offset_x,
									  self->gpu_offset_y ,
									  self->d_cos_s , self->d_sin_s , self->d_axis_s,
									  Nfirstslice, nslices,
									  data_start, nslices_data,
									  source_distance, detector_distance,
									  v2x, v2z, voxel_size,
									  SOURCE_X, SOURCE_Z, dectTilt);
    }
  }


  // CUDA_SAFE_CALL( cudaMemcpy( SLICE, d_SLICE, sizeof(float) * nslices*self->dimrecx* self->dimrecy , cudaMemcpyDeviceToHost) );
  CUDA_SAFE_CALL( cudaMemcpy( SLICE, d_SLICE, sizeof(float) * nslices*self->num_x* self->num_y , cudaMemcpyDeviceToHost) );

  CUDA_SAFE_CALL(cudaFreeArray(a_Proje_voidptr));
  CUDA_SAFE_CALL(cudaFree(d_SLICE));

  return 1;
}

int       pro_gpu_main_conicity(Gpu_Context * self,  float * SLICE, float *SINO ,
				int Nfirstslice, int nslices, int data_start, int nslices_data,
				float source_distance, float detector_distance,
				float v2x ,   float v2z,float voxel_size,
				float SOURCE_X , float SOURCE_Z ){

  cuCtxSetCurrent ( *((CUcontext *) self->gpuctx  ))  ;
  cudaArray * a_SLICE_voidptr ;

  {
    cudaExtent extent;
    extent.width = self->num_x;
    extent.height = self->num_y;
    extent.depth= nslices+2;
    cudaChannelFormatDesc ch_desc = cudaCreateChannelDesc<float>();

    CUDA_SAFE_CALL( cudaMalloc3DArray(&a_SLICE_voidptr, &ch_desc, extent, 0));
  }
  {
    cudaMemcpy3DParms params;

    memset(&params, 0, sizeof(params));
    params.srcPtr.pitch = sizeof(float) * self->num_x;
    params.srcPtr.ptr = SLICE;
    params.srcPtr.xsize =  self->num_x;
    params.srcPtr.ysize = self->num_y;

    params.srcPos.x = 0;
    params.srcPos.y = 0;
    params.srcPos.z = 0;

    params.dstArray = a_SLICE_voidptr;

    params.dstPos.x = 0;
    params.dstPos.y = 0;
    params.dstPos.z = 1;

    params.extent.width  = self->num_x;
    params.extent.height  = self->num_y;
    params.extent.depth = nslices ;
    params.kind = cudaMemcpyHostToDevice;
    CUDA_SAFE_CALL( cudaMemcpy3D(&params););
    float *zeros = (float*)malloc(  sizeof(float) * self->num_x*self->num_y   );
    memset(zeros, 0,  sizeof(float) * self->num_x*self->num_y  );

    params.dstPos.z = 0;
    params.extent.depth = 1 ;
    params.srcPtr.ptr = zeros;
    CUDA_SAFE_CALL( cudaMemcpy3D(&params););
    params.dstPos.z = nslices+1 ;
    CUDA_SAFE_CALL( cudaMemcpy3D(&params););
  }
  CUDA_SAFE_CALL( cudaBindTextureToArray(texfor3D,a_SLICE_voidptr) );
  texfor3D.filterMode     =   cudaFilterModeLinear;
  texfor3D.addressMode[0] =   cudaAddressModeBorder;
  texfor3D.addressMode[1] =   cudaAddressModeBorder;
  texfor3D.addressMode[2] =   cudaAddressModeClamp;
  texfor3D.normalized = false;


  int pezzo_z=8, pezzo_x=8, pezzo_y=8;
  if(nslices_data<8) {
    pezzo_z=4, pezzo_x=32, pezzo_y=4;
  }
  if(nslices_data<4) {
    pezzo_z=2, pezzo_x=32, pezzo_y=4;
  }
  if(nslices_data<2) {
    pezzo_z=1, pezzo_x=32, pezzo_y=4;
  }
  int nx,ny,nz;
  nx = ( (int ) (self->num_bins*1.0/(pezzo_x * PROCONO_GPU_MULT)  +0.999999));
  ny = ( (int ) (self->nprojs_span*1.0/pezzo_y +0.999999));
  nz = ( (int ) (nslices_data*1.0/(pezzo_z* PROCONO_GPU_MULT) +0.999999));
  
  DectTilt dectTilt = {  sin(self->tilt_psi), cos(self->tilt_psi), sin(self->tilt_tilt), cos(self->tilt_tilt)  } ; 
  
  float *d_SINO;
  CUDA_SAFE_CALL(cudaMalloc(  &(d_SINO) , sizeof(float) *nslices_data *self->nprojs_span*    self->num_bins ));
  CUDA_SAFE_CALL(cudaMemset( d_SINO,0, sizeof(float) * nslices_data *self->nprojs_span*    self->num_bins  ));
  {
    dim3 dimGrid ( nx ,  ny, nz  );
    dim3      dimBlock(  pezzo_x, pezzo_y, pezzo_z  );
    if(self->verbosity>0) { printf(" CHIAMO conicity  KERNEL \n"); }
    multi_pro_gputomo_conicity_kernel<<<dimGrid,dimBlock>>> ( d_SINO, // da allocare
							      self->nprojs_span,
							      nslices_data,
							      self->num_bins,
							      self->axis_position,
							      self->gpu_offset_x,
							      self->gpu_offset_y ,
							      self->d_cos_s , self->d_sin_s , self->d_axis_s,
							      Nfirstslice, nslices,
							      self->num_x, self->num_y,
							      data_start,
							      source_distance, detector_distance,
							      v2x, v2z, voxel_size,
							      SOURCE_X, SOURCE_Z,
							      dectTilt);
  }
  CUDA_SAFE_CALL( cudaMemcpy( SINO, d_SINO, sizeof(float) * nslices_data *self->nprojs_span*    self->num_bins , cudaMemcpyDeviceToHost) );

  if(self->verbosity>0) {  printf(" CHIAMO KERNEL OK\n"); }
  CUDA_SAFE_CALL(cudaFreeArray(a_SLICE_voidptr));
  CUDA_SAFE_CALL(cudaFree(d_SINO));
  return 1;
}

#define checkCudaErrors(err)  __checkCudaErrors (err, __FILE__, __LINE__)

inline void __checkCudaErrors(cudaError err, const char *file, const int line )
{
  if(cudaSuccess != err)
    {
      fprintf(stderr, "%s(%i) : CUDA Runtime API error %d: %s.\n",file, line, (int)err, cudaGetErrorString( err ) );
      exit(-1);
    }
}

int dfi_gpu_main(Gpu_Context * self, float *WORK , float * SLICE, int memisonhost = 1) {
  cuCtxSetCurrent ( *((CUcontext *) self->gpuctx  ));

  dim3 dimPadGrid(self->dfi_params.pps_grid_cols, self->dfi_params.pps_grid_rows);
  dim3 dimInterpGrid(self->dfi_params.interp_grid_cols, self->dfi_params.interp_grid_rows);
  dim3 dimSwapGrid(self->dfi_params.swap_grid_cols, self->dfi_params.swap_grid_rows);
  dim3 dimSwapQuadsGrid(self->dfi_params.swap_quad_grid_cols, self->dfi_params.swap_quad_grid_rows);
  dim3 dimRoiGrid(self->dfi_params.roi_grid_cols, self->dfi_params.roi_grid_rows);
  dim3 dimBlock(BLOCK_SIZE, BLOCK_SIZE);


  cudaMemcpyKind copy1, copy2;
  if (memisonhost) {
    copy1 = cudaMemcpyHostToDevice;
    copy2 = cudaMemcpyDeviceToHost;
    CUDA_SAFE_CALL(cudaMemcpy2D(self->dfi_params.gpu_truncated_sino,
				self->dfi_params.rho_len * sizeof(float),
				WORK,
				self->dim_fft * sizeof(float),
				self->dfi_params.rho_len * sizeof(float),
				self->dfi_params.nprojs_span,
				copy1));
  }
  else {
    copy1 = cudaMemcpyDeviceToDevice;
    copy2 = cudaMemcpyDeviceToDevice;
    CUDA_SAFE_CALL(cudaMemcpy2D(self->dfi_params.gpu_truncated_sino,
				self->dfi_params.rho_len * sizeof(float),
				WORK,
				self->num_bins * sizeof(float), // when DFI is used for data on device, the sinogram is not padded
				self->dfi_params.rho_len * sizeof(float),
				self->dfi_params.nprojs_span,
				copy1));
  }

  /*
    FILE* fid = fopen("tmp.dat", "wb");
    fwrite(WORK, sizeof(float), self->dfi_params.num_projections*self->dim_fft, fid);
    fclose(fid);
    fid = fopen("tmp2.dat", "wb");
    float* trunc_sino = (float*) calloc(self->dfi_params.rho_len*self->dfi_params.num_projections,sizeof(float));
    cudaMemcpy(trunc_sino, self->dfi_params.gpu_truncated_sino, self->dfi_params.rho_len*self->dfi_params.num_projections*sizeof(float), cudaMemcpyDeviceToHost);
    fwrite(trunc_sino, sizeof(float), self->dfi_params.num_projections*self->dfi_params.rho_len, fid);
    fclose(fid);
  */


  if (self->DFI_R2C_MODE) {
    dfi_cuda_zeropadding_real<<<dimPadGrid,dimBlock>>>(self->dfi_params.gpu_truncated_sino,
						       self->dfi_params.rho_len,
						       self->dfi_params.fft_sino_dim,
						       self->dfi_params.gpu_zeropadded_sino);

    CUDA_SAFE_FFT(cufftExecR2C(self->dfi_params.fft1d_plan,
			       (cufftReal *)self->dfi_params.gpu_zeropadded_sino,
			       (cufftComplex *)self->dfi_params.gpu_zeropadded_sino));
  }
  else {
    dfi_cuda_zeropadding_complex<<<dimPadGrid,dimBlock>>>((cufftReal *)self->dfi_params.gpu_truncated_sino,
							  self->dfi_params.rho_len,
							  (cufftComplex *)self->dfi_params.gpu_input);

    CUDA_SAFE_FFT(cufftExecC2C(self->dfi_params.fft1d_plan,
			       (cufftComplex *)self->dfi_params.gpu_input,
			       (cufftComplex *)self->dfi_params.gpu_input,
			       CUFFT_FORWARD));
  }
  if (self->DFI_R2C_MODE) {
    CUDA_SAFE_CALL(cudaMallocArray((cudaArray**)&(self->dfi_params.gpu_input_cua),
				   &(dfi_tex_projections.channelDesc),
				   self->dfi_params.fft_sino_dim/2,
				   self->dfi_params.nprojs_span));

    CUDA_SAFE_CALL(cudaMemcpy2DToArray((cudaArray*)self->dfi_params.gpu_input_cua,
				       0,
				       0,
				       self->dfi_params.gpu_zeropadded_sino,
				       self->dfi_params.fft_sino_dim / 2 * sizeof(cufftComplex),
				       self->dfi_params.fft_sino_dim / 2 * sizeof(cufftComplex),
				       self->dfi_params.nprojs_span,
				       cudaMemcpyHostToDevice));


    CUDA_SAFE_CALL(cudaBindTextureToArray(dfi_tex_projections, (cudaArray*)self->dfi_params.gpu_input_cua));
  }
  else {
    CUDA_SAFE_CALL(cudaMallocArray((cudaArray**)&(self->dfi_params.gpu_input_cua),
				   &(dfi_tex_projections.channelDesc),
				   self->dfi_params.rho_ext_len,
				   self->dfi_params.nprojs_span));

    CUDA_SAFE_CALL(cudaMemcpy2DToArray((cudaArray*)self->dfi_params.gpu_input_cua,
				       0,
				       0,
				       self->dfi_params.gpu_input,
				       self->dfi_params.rho_ext_len * sizeof(cufftComplex),
				       self->dfi_params.rho_ext_len * sizeof(cufftComplex),
				       self->dfi_params.nprojs_span,
				       cudaMemcpyHostToDevice));

    CUDA_SAFE_CALL(cudaBindTextureToArray(dfi_tex_projections, (cudaArray*)self->dfi_params.gpu_input_cua));
  }
  CUDA_SAFE_CALL(cudaMallocArray((cudaArray**)&(self->dfi_params.gpu_ktbl),
				 &(dfi_tex_ktbl.channelDesc),
				 self->dfi_params.ktbl_len));

  CUDA_SAFE_CALL(cudaMemcpyToArray((cudaArray*)self->dfi_params.gpu_ktbl,
				   0,
				   0,
				   self->dfi_params.ktbl,
				   (self->dfi_params.ktbl_len) * sizeof(float),
				   cudaMemcpyHostToDevice)); //dfi_params.ktbl is a host array

  CUDA_SAFE_CALL(cudaBindTextureToArray(dfi_tex_ktbl, (cudaArray*)self->dfi_params.gpu_ktbl));

  dfi_cuda_interpolation_sinc<<<dimInterpGrid,dimBlock>>>(self->dfi_params.spectrum_offset_y,
							  self->dfi_params.L2,
							  self->dfi_params.ktbl_len2,
							  self->dfi_params.raster_size,
							  self->dfi_params.raster_size2,
							  self->dfi_params.table_spacing,
							  self->dfi_params.angle_step_rad,
							  self->dfi_params.theta_max,
							  self->dfi_params.rho_max,
							  (self->dfi_params.raster_size/2 + 1),
							  self->dfi_params.max_radius,
							  self->dfi_params.gpu_spectrum);

  cudaUnbindTexture(dfi_tex_projections);
  cudaUnbindTexture(dfi_tex_ktbl);

  dfi_cuda_swap_quadrants_complex<<<dimSwapGrid,dimBlock>>>(self->dfi_params.gpu_spectrum,
							    self->dfi_params.gpu_swapped_spectrum,
							    (self->dfi_params.raster_size/2 + 1));
  cufftExecC2R(self->dfi_params.ifft2d_plan, self->dfi_params.gpu_swapped_spectrum, self->dfi_params.gpu_c2r_result);

  dfi_cuda_swap_quadrants_real<<<dimSwapQuadsGrid,dimBlock>>>(self->dfi_params.gpu_c2r_result);

  dfi_cuda_crop_roi<<<dimRoiGrid,dimBlock>>>(self->dfi_params.gpu_c2r_result,
					     self->dfi_params.roi_start_x,
					     self->dfi_params.roi_start_y,
					     self->dfi_params.roi_x,
					     self->dfi_params.roi_y,
					     self->dfi_params.raster_size,
					     self->dfi_params.scale,
					     self->dfi_params.gpu_output);

  cudaMemset(self->dfi_params.gpu_swapped_spectrum,
	     0,
	     (self->dfi_params.raster_size/2 + 1) * self->dfi_params.raster_size * sizeof(cufftComplex));

  cudaMemset(self->dfi_params.gpu_spectrum,
	     0,
	     (self->dfi_params.raster_size/2 + 1) * self->dfi_params.raster_size * sizeof(cufftComplex));
  /*
    FILE* fid2 = fopen("tmp3.dat", "wb");
    float* tmparr = (float*) calloc(self->dfi_params.roi_x * self->dfi_params.roi_y, sizeof(float));
    cudaMemcpy(tmparr, SLICE, sizeof(float)* self->dfi_params.roi_x * self->dfi_params.roi_y, cudaMemcpyDeviceToHost);
    fwrite(tmparr, sizeof(float), self->dfi_params.roi_x * self->dfi_params.roi_y, fid2);
    fclose(fid2);
    puts("wrote !");
  */

  cudaMemcpy(SLICE, self->dfi_params.gpu_output, self->dfi_params.roi_x * self->dfi_params.roi_y * sizeof(float), copy2);
  cudaFreeArray((cudaArray*) self->dfi_params.gpu_input_cua);
  cudaFreeArray((cudaArray*) self->dfi_params.gpu_ktbl);
  CUDACHECK;

  return 0;
}

void dfi_gpuFree( Gpu_Context *   self  )  { //FIXME : it is not used
  cuCtxSetCurrent ( *((CUcontext *) self->gpuctx  ));

  if (self->dfi_params.fft1d_plan)
    CUDA_SAFE_FFT(cufftDestroy(self->dfi_params.fft1d_plan));

  if (self->dfi_params.ifft2d_plan)
    CUDA_SAFE_FFT(cufftDestroy(self->dfi_params.ifft2d_plan));

  if (self->DFI_R2C_MODE) {
    if (self->dfi_params.gpu_zeropadded_sino)
      CUDA_SAFE_CALL(cudaFree(self->dfi_params.gpu_zeropadded_sino));
  }
  else {
    if (self->dfi_params.gpu_input)
      CUDA_SAFE_CALL(cudaFree(self->dfi_params.gpu_input));
  }

  if (self->dfi_params.gpu_output)
    CUDA_SAFE_CALL(cudaFree(self->dfi_params.gpu_output));

  if (self->dfi_params.gpu_spectrum)
    CUDA_SAFE_CALL(cudaFree(self->dfi_params.gpu_spectrum));

  if (self->dfi_params.gpu_swapped_spectrum)
    CUDA_SAFE_CALL(cudaFree(self->dfi_params.gpu_swapped_spectrum));

  if (self->dfi_params.gpu_truncated_sino)
    CUDA_SAFE_CALL(cudaFree(self->dfi_params.gpu_truncated_sino));

  if (self->dfi_params.gpu_ktbl)
    CUDA_SAFE_CALL(cudaFree(self->dfi_params.gpu_ktbl));

  if (self->dfi_params.gpu_c2r_result)
    CUDA_SAFE_CALL(cudaFree(self->dfi_params.gpu_c2r_result));

  if (self->dfi_params.ktbl)
    free(self->dfi_params.ktbl);
}

void dfp_gpuFree( Gpu_Context *   self  ) {
  if (self->dfp_params.ktbl)
    free(self->dfp_params.ktbl);

  if (self->dfp_params.gpu_ktbl)
    CUDA_SAFE_CALL(cudaFree(self->dfp_params.gpu_ktbl));

  if (self->dfp_params.gpu_zeropadded_slice)
    CUDA_SAFE_CALL(cudaFree(self->dfp_params.gpu_zeropadded_slice));

  if (self->dfp_params.gpu_reconstructed_sinogram)
    CUDA_SAFE_CALL(cudaFree(self->dfp_params.gpu_reconstructed_sinogram));

  if (self->dfp_params.gpu_reconstructed_sinogram_real)
    CUDA_SAFE_CALL(cudaFree(self->dfp_params.gpu_reconstructed_sinogram_real));

  if (self->dfp_params.gpu_reconstructed_crop_sinogram_real)
    CUDA_SAFE_CALL(cudaFree(self->dfp_params.gpu_reconstructed_crop_sinogram_real));

  cudaUnbindTexture(dfp_tex_ktbl);
}

int gpu_main(Gpu_Context * self, float *WORK , float * SLICE, int do_precondition,
	     float DETECTOR_DUTY_RATIO,
	     int DETECTOR_DUTY_OVERSAMPLING,
	     float * fidelity,
	     int npj_offset, int forcePImultipl ) {


  cuCtxSetCurrent ( *((CUcontext *) self->gpuctx  ))  ;

  //CUDA_SAFE_CALL(


  cudaError_t err = cudaMemcpy(self->dev_rWork,WORK,sizeof(float)*(self->dim_fft*self->nprojs_span),cudaMemcpyHostToDevice);
  if(err!=cudaSuccess) {
    printf("Last error-reset: %s \n", cudaGetErrorString(err ));
    exit(1);
  }
  if(fidelity) *fidelity=0;
  if(1) {
    if(fidelity && do_precondition==0) {
      *fidelity = cublasSnrm2( self->nprojs_span*self->dim_fft, self->dev_rWork,1  )  ;
    }
  } else {
    // qua per cross validation
    if(fidelity && do_precondition==0) {
      *fidelity = cublasSnrm2( self->dim_fft, self->dev_rWork,1  )  ;
      cudaMemset(self->dev_rWork,0,sizeof(float)*(self->dim_fft));
    }
  }


  if( (self->FBFILTER>=0 && do_precondition) ||  self->FBFILTER ==2 || self->SF_ITERATIONS) {
    int iproj;
    for(iproj=0; iproj<self->nprojs_span; iproj+=fftbunch) {
      CUDA_SAFE_FFT(cufftExecR2C((cufftHandle) self->planR2C,
                                 self->dev_rWork+(iproj*self->dim_fft),
                                 (cufftComplex *) self->dev_iWork));

      if(do_precondition ){
        int bunch_size = fftbunch;
        if (iproj + bunch_size > self->nprojs_span) bunch_size = self->nprojs_span-iproj;
        dim3 blk,grd;
        int Hdimfft = self->dim_fft/2 +1 ;
        blk = dim3( blsize_cufft , 1 , 1 );
        grd = dim3( iDivUp((Hdimfft) ,blsize_cufft) , 1, 1 );
        // kern_cufft_filter<<<grd,blk>>>(Hdimfft ,(cufftReal*)(self->dev_iWork),self->dev_Filter,self->nprojs_span, self->dim_fft);
        if(fidelity)  cudaMemcpy(self->dev_iWork_copy,self->dev_iWork,
                                 sizeof(cufftComplex)*Hdimfft*fftbunch,
                                 cudaMemcpyDeviceToDevice);
        if (self->SF_ITERATIONS) {
          dim3 blk_sf, grd_sf;
          blk_sf = dim3(blsize_cufft, blsize_cufft, 1);
          grd_sf = dim3(iDivUp((Hdimfft), blsize_cufft), iDivUp((fftbunch), blsize_cufft), 1);
          kern_cufft_filter_angledependent<<<grd_sf, blk_sf>>>(Hdimfft ,(cufftReal*)(self->dev_iWork), self->dev_Filter, fftbunch, self->dim_fft, iproj);
        }
        else kern_cufft_filter<<<grd,blk>>>(Hdimfft ,(cufftReal*)(self->dev_iWork),self->dev_Filter,fftbunch, self->dim_fft);

        if(cudaError_t err=cudaGetLastError()){printf("kern_cufft_filter: %s\n",cudaGetErrorString(err));/*getchar();*/}
        if(fidelity) {
          kern_cufft_filter_corrpersymm<<<grd,blk>>>(Hdimfft ,(cufftReal*)(self->dev_iWork_copy),fftbunch, self->dim_fft);
          *fidelity+= cublasSdot( Hdimfft*bunch_size*2,
                                  (float*) self->dev_iWork, 1,(float*) self->dev_iWork_copy, 1)  ;
        }
      }

      if(self->FBFILTER ==2) {
        dim3 blk,grd;
        int Hdimfft = self->dim_fft/2 +1 ;
        blk = dim3( blsize_cufft , 1 , 1 );
        grd = dim3( iDivUp((Hdimfft) ,blsize_cufft) , 1, 1 );
        kern_cufft_filter_talbot<<<grd,blk>>>(Hdimfft ,(cufftReal*)(self->dev_iWork),fftbunch, self->dim_fft);
        if(cudaError_t err=cudaGetLastError()){printf("kern_cufft_filterr_talbot: %s\n",cudaGetErrorString(err));/*getchar();*/}
      }

      CUDA_SAFE_FFT(cufftExecC2R((cufftHandle) self->planC2R,(cufftComplex *)self->dev_iWork,
                                 self->dev_rWork+ (iproj*self->dim_fft)   ));
    } // end of loop on projections
  }

  {
    dim3 blk,grd;
    blk = dim3( blsize_cufft , 1 , 1 );
    grd = dim3( iDivUp(self->num_bins,blsize_cufft) , 1, 1 );
    if(  self->fai360==0) {
      kern_recopy<<<grd,blk>>>(self->num_bins,self->dim_fft,self->nprojs_span, self->dev_Work_perproje,  self->dev_rWork
                               );
    } else {
      kern_recopy_slope<<<grd,blk>>>(self->num_bins,self->dim_fft,self->nprojs_span, self->dev_Work_perproje,  self->dev_rWork,
                                     self->overlapping, self->pente_zone, self->flat_zone,
                                     self->d_axis_s,
                                     self->prof_shift  , self->prof_fact
                                     );
    }
  }

  CUDA_SAFE_CALL( cudaMemcpyToArray((cudaArray*) self->a_Proje_voidptr, 0, 0,self->dev_Work_perproje ,
				    self->nprojs_span*self->num_bins*sizeof(float) , cudaMemcpyDeviceToDevice) );
  texProjes.addressMode[0] = cudaAddressModeBorder;
  texProjes.addressMode[1] = cudaAddressModeClamp;
  texProjes.filterMode = cudaFilterModeLinear;
  CUDA_SAFE_CALL( cudaBindTextureToArray(texProjes,(cudaArray*) self->a_Proje_voidptr) );  //  !! unbind ??


  int inizio=0 , fine=0;
  float fattore_estremi=1;
  if(self->DZPERPROJ!=0.0) {
    int startpro=-1;
    int endpro   =-1;

    float startangle=-1;
    int endpro_angle =-1;
    int npis=0;
    int projection;
    
    for(projection=0; projection < self->nprojs_span ; projection++) {

      int npj_absolute = self->tot_proj_num_list[projection ] + npj_offset ;

      int isgood=0;
      if( npj_absolute<0 || npj_absolute >=  self->numpjs) {
	isgood=0;
      } else {
	int bin = self->axis_position;
	if( WORK[bin+projection*self->dim_fft]!= WORK[bin+projection*self->dim_fft]) {
	  isgood=0;
	} else {
	  isgood=1;
	}
      }
      if(isgood) {
	if(startpro == -1) {
	  startpro = projection;
	  if(forcePImultipl ) {
	    startangle = self->angles_per_proj[npj_absolute] ;
	  }
	} else {
	  if(forcePImultipl) {
	    if( fabs(-startangle + 2*self->angles_per_proj[npj_absolute]-self->angles_per_proj[npj_absolute-1]   ) > (npis+1.00001)*M_PI) {
	      if( fabs(fabs(-startangle + self->angles_per_proj[npj_absolute]) - (npis+1)*M_PI )<1.0e-6 *M_PI  ) {
		fattore_estremi=0.5;
	      } else {
		fattore_estremi=3.0/4;
	      }
	      endpro_angle = projection+1;
	      npis++;
	    }
	  }
	}
      } else {
	if(startpro != -1 && endpro == -1) endpro = projection;
      }
    }
    if(forcePImultipl) {
      endpro =  endpro_angle  ;
    }

    if(forcePImultipl) {
      inizio = startpro ; // i casi estremi sono gestiti dal kernel se fattoreestremi!=1
      fine   = endpro   ;
    } else {
      inizio = startpro;
      fine   = endpro  ;
    }
  }
  // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

  if(self->DZPERPROJ==0.0) {
    dim3      dimBlock         (  16  ,  16   );
    int y = 0;
    int itime=0;
    {
      dim3 dimGrid ( self->NblocchiPerLinea32 ,   self->NblocchiPerColonna32  );
      // dim3 dimGrid ( self->NblocchiPerLinea ,   self->NblocchiPerColonna  );

      if(self->CONICITY_FAN ==0) {
	gputomo_kernel32<<<dimGrid,dimBlock,256*3*sizeof(float),(((Gpu_Streams*) self->gpu_streams)->stream[itime%2])>>>
	  (self->nprojs_span,self->num_bins, self->axis_position,
	   self->d_SLICE,
	   self->gpu_offset_x, self->gpu_offset_y + y,
	   self->d_cos_s , self->d_sin_s , self->d_axis_s
	   );
      } else  {
	gputomo_kernel32_fan<<<dimGrid,dimBlock,256*3*sizeof(float),(((Gpu_Streams*) self->gpu_streams)->stream[itime%2])>>>
	  (self->nprojs_span,self->num_bins, self->axis_position,
	   self->d_SLICE,
	   self->gpu_offset_x, self->gpu_offset_y + y,
	   self->d_cos_s , self->d_sin_s , self->d_axis_s,  self->FAN_FACTOR,
	   self->SOURCE_X
	   );
      }
    }
  }  else {
    int ipoffset;
    ipoffset =  self->tot_proj_num_list[0] + npj_offset ;

    dim3      dimBlock         (  16  ,  16   );
    int y = 0;
    int itime=0;
    {
      dim3 dimGrid ( self->NblocchiPerLinea32 ,   self->NblocchiPerColonna32  );
      // dim3 dimGrid ( self->NblocchiPerLinea ,   self->NblocchiPerColonna  );

      if(self->CONICITY_FAN ==0) {
	gputomo_kernel32_heli<<<dimGrid,dimBlock,256*3*sizeof(float),(((Gpu_Streams*) self->gpu_streams)->stream[itime%2])>>>
	  (self->nprojs_span,self->num_bins, self->axis_position,
	   self->d_SLICE,
	   self->gpu_offset_x, self->gpu_offset_y + y,
	   self->d_cos_s , self->d_sin_s , self->d_axis_s,
	   inizio, fine,
	   fattore_estremi,
	   ipoffset
	   );
      } else  {
	gputomo_kernel32_fan_heli<<<dimGrid,dimBlock,256*3*sizeof(float),(((Gpu_Streams*) self->gpu_streams)->stream[itime%2])>>>
	  (self->nprojs_span,self->num_bins, self->axis_position,
	   self->d_SLICE,
	   self->gpu_offset_x, self->gpu_offset_y + y,
	   self->d_cos_s , self->d_sin_s , self->d_axis_s,  self->FAN_FACTOR,
	   self->SOURCE_X,
	   inizio, fine,
	   fattore_estremi,
	   ipoffset
	   );
      }
    }
  }


  if(abs(DETECTOR_DUTY_OVERSAMPLING)>1){
    assert( self->num_x==self->num_y  );
    int dimslice = self->num_x ;
    int NblocchiPerLinea   =   self->NblocchiPerLinea   ;
    // int NblocchiPerColonna =   self->NblocchiPerColonna ;
    int dimrecx; // , dimrecy;
    dimrecx = NblocchiPerLinea  *16 ;
    // dimrecy = NblocchiPerColonna*16 ;

    float dangle = acos(self->cos_s[1]*self->cos_s[0]+self->sin_s[1]*self->sin_s[0])*DETECTOR_DUTY_RATIO;

    dim3 dimGrid ( NblocchiPerLinea ,   NblocchiPerLinea );
    dim3      dimBlock         (  16  ,  16   );

    // printf(" self->NblocchiPerLinea,  self->NblocchiPerColonna  %d %d \n" ,  self->NblocchiPerLinea,  self->NblocchiPerColonna  );
    // printf(" dimslice dimrecy  dimrecy %d  %d %d \n" , dimslice, dimrecy , dimrecy  );

    cudaArray * a_slice;
    // cudaChannelFormatDesc floatTex = cudaCreateChannelDesc<float>();

    CUDA_SAFE_CALL( cudaMallocArray(&a_slice, &floatTex ,  (dimslice+2)   ,  (dimslice+2)) );


    {
      float zeri[dimslice+2];
      memset(zeri,0, (dimslice+2)*sizeof(float));
      CUDA_SAFE_CALL( cudaMemcpy2DToArray(a_slice, 0, 0, zeri , (dimslice+2)*sizeof(float) ,(dimslice+2)*sizeof(float) , 1,cudaMemcpyHostToDevice) );
      CUDA_SAFE_CALL( cudaMemcpy2DToArray(a_slice, 0, 0, zeri , (1)*sizeof(float) ,(1)*sizeof(float) ,(dimslice+2) ,cudaMemcpyHostToDevice) );
      CUDA_SAFE_CALL( cudaMemcpy2DToArray(a_slice, (dimslice+1)*4, 0, zeri , (1)*sizeof(float) ,
					  (1)*sizeof(float) ,(dimslice+2) ,cudaMemcpyHostToDevice) );
      CUDA_SAFE_CALL( cudaMemcpy2DToArray(a_slice, 0, dimslice+1, zeri , (dimslice+2)*sizeof(float) ,(dimslice+2)*sizeof(float) , 1,cudaMemcpyHostToDevice) );
    }

    CUDA_SAFE_CALL( cudaMemcpy2DToArray(a_slice, 1*4, 1, self->d_SLICE , (dimrecx)*sizeof(float) ,(dimslice)*sizeof(float) , dimslice,
					cudaMemcpyDeviceToDevice) );


    texProjes.filterMode = cudaFilterModeLinear;
    texProjes.addressMode[0] = cudaAddressModeClamp;
    texProjes.addressMode[1] = cudaAddressModeClamp;
    texProjes.filterMode = cudaFilterModeLinear;
    texProjes.normalized = false;
    CUDA_SAFE_CALL( cudaBindTextureToArray(texProjes,(cudaArray*) a_slice) );  //  !! unbind ??


    // printf("gputomo.cu: cpu_main  DOING slicerot_kernel with DETECTOR_DUTY_OVERSAMPLING=%d\n",DETECTOR_DUTY_OVERSAMPLING);
    slicerot_kernel<<<dimGrid,dimBlock>>>( dimslice,
					   self->d_SLICE,
					   dangle,
					   abs(DETECTOR_DUTY_OVERSAMPLING) );



    CUDA_SAFE_CALL(cudaFreeArray(a_slice));


  }
  int y=0;
  cudaMemcpy2D(
	       // CUDA_SAFE_CALL(
	       // cudaMemcpy2DAsync(
	       SLICE+y*self->num_x,self->num_x*sizeof(float),
	       self->d_SLICE ,
	       self->dimrecx*sizeof(float),
	       self->num_x*sizeof(float),
	       self->num_y,
	       cudaMemcpyDeviceToHost
	       // ,
	       //  (((Gpu_Streams*) self->gpu_streams)->stream[itime%2])
	       );
  //  );
  cudaError_t last = cudaGetLastError();
  if(last!=cudaSuccess) {
    printf("memcpy async Last error-reset: %s \n", cudaGetErrorString( last));
    exit(1);
  }
  // t2 = clock();
  // printf( "GPUKRNEL OK t=%f \n",  ( ((long)t2)-((long)t1)   )*1.0f/CLOCKS_PER_SEC);

  return 1;
}

int gpu_main_2by2(Gpu_Context * self, float *WORK , float * SLICE, int do_precondition,
	     int npj_offset, int forcePImultipl ) {



  assert(self->SF_ITERATIONS==0  ) ; 
  cuCtxSetCurrent ( *((CUcontext *) self->gpuctx  ))  ;

  int inizio[]={0,0} , fine[]={0,0};
  float fattore_estremi[]={1.0,1.0};
  if(self->DZPERPROJ!=0.0) {

    for(int ks = 0; ks< TWO2by2; ks++) {
    
      int startpro=-1;
      int endpro   =-1;
      float startangle=-1;
      int endpro_angle =-1;
      int npis=0;
      int projection;


      // 2by2   raddoppiare startpro endpro  fattori estremi

    
      for(projection=0; projection < self->nprojs_span ; projection++) {

	int npj_absolute = self->tot_proj_num_list[projection ] + npj_offset ;

	int isgood=0;
	if( npj_absolute<0 || npj_absolute >=  self->numpjs) {
	  isgood=0;
	} else {
	  int bin = self->axis_position;
	  if( WORK[(bin+projection*self->dim_fft)*TWO2by2+ks] != WORK[(bin+projection*self->dim_fft)*TWO2by2+ks]                 ) {  // ricerca Nans
	    isgood=0;
	  } else {
	    isgood=1;
	  }
	}
	if(isgood) {
	  if(startpro == -1) {
	    startpro = projection;
	    if(forcePImultipl ) {
	      startangle = self->angles_per_proj[npj_absolute] ;
	    }
	  } else {
	    if(forcePImultipl) {
	      if( fabs(-startangle + 2*self->angles_per_proj[npj_absolute]-self->angles_per_proj[npj_absolute-1]   ) > (npis+1.00001)*M_PI) {
		if( fabs(fabs(-startangle + self->angles_per_proj[npj_absolute]) - (npis+1)*M_PI )<1.0e-6 *M_PI  ) {
		  fattore_estremi[ks]=0.5;
		} else {
		  fattore_estremi[ks]=3.0/4;
		}
		endpro_angle = projection+1;
		npis++;
	      }
	    }
	  }
	} else {
	  if(startpro != -1 && endpro == -1) endpro = projection;
	}
      }
      if(forcePImultipl) {
	endpro =  endpro_angle  ;
      }

      if(forcePImultipl) {
	inizio[ks] = startpro ; // i casi estremi sono gestiti dal kernel se fattoreestremi!=1
	fine[ks]   = endpro   ;
      } else {
	inizio[ks] = startpro;
	fine  [ks] = endpro  ;
      }
    }
  }
  // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@



  

  // WORK deve essere gia interleaved
  cudaError_t err = cudaMemcpy(self->dev_rWork,WORK,TWO2by2*sizeof(float)*(self->dim_fft*self->nprojs_span),cudaMemcpyHostToDevice); // cWork deve essere lo stesso di rWork, cioe' rWork e'contenuto in cwork  2by2  OK
  if(err!=cudaSuccess) {    printf("Last error-reset: %s \n", cudaGetErrorString(err ));    exit(1);  }

  {  
    dim3 blk,grd;
    blk = dim3( blsize_cufft , 1 , 1 );
    grd = dim3( iDivUp(    TWO2by2*self->dim_fft       ,blsize_cufft) , 1, 1 );

    kern_filter_nans<<<grd,blk>>>(self->dim_fft*TWO2by2,self->nprojs_span,  self->dev_rWork   );
    
  }

  
  if( (self->FBFILTER>=0 && do_precondition) ||  self->FBFILTER ==2  ) {
    int iproj;
    for(iproj=0; iproj<self->nprojs_span; iproj+=fftbunch) {
      CUDA_SAFE_FFT(cufftExecC2C((cufftHandle) self->planC2C_2by2,                  // 2by2 creare piano C2C OK
                                 (cufftComplex *) (self->dev_rWork+(iproj*self->dim_fft)*TWO2by2),    // 2by2 cwork deve essere float pero  OK
                                 (cufftComplex *) self->dev_iWork,                   // 2by2  anche iwork deve essere allocato al doppio OK
				 CUFFT_FORWARD)
		    );
      
      if(do_precondition ){
	
        int bunch_size = fftbunch;
        if (iproj + bunch_size > self->nprojs_span) bunch_size = self->nprojs_span-iproj;
        dim3 blk,grd;
        // int Hdimfft = self->dim_fft/2 +1 ;
        int Hdimfft = self->dim_fft ;    // 2by2  OK 
        blk = dim3( blsize_cufft , 1 , 1 );
        grd = dim3( iDivUp((Hdimfft) ,blsize_cufft) , 1, 1 );
	
        kern_cufft_filter<<<grd,blk>>>(Hdimfft ,(cufftReal*)(self->dev_iWork),self->dev_Filter,fftbunch, self->dim_fft);  // 2by2 dovrebbe andare bene cosi e se no i risultati sono disastrosi
	
        if(cudaError_t err=cudaGetLastError()){printf("kern_cufft_filter: %s\n",cudaGetErrorString(err));/*getchar();*/}
	
      }

      if(self->FBFILTER ==2) {
        dim3 blk,grd;
        // int Hdimfft = self->dim_fft/2 +1 ;
        int Hdimfft = self->dim_fft  ;   // 2by2  idem
        blk = dim3( blsize_cufft , 1 , 1 );
        grd = dim3( iDivUp((Hdimfft) ,blsize_cufft) , 1, 1 );
        kern_cufft_filter_talbot_2by2<<<grd,blk>>>(Hdimfft ,(cufftReal*)(self->dev_iWork),fftbunch, self->dim_fft);  // 2by2 idem
        if(cudaError_t err=cudaGetLastError()){printf("kern_cufft_filterr_talbot_2by2: %s\n",cudaGetErrorString(err));/*getchar();*/}
      }
      
      CUDA_SAFE_FFT(cufftExecC2C((cufftHandle) self->planC2C_2by2,(cufftComplex *)self->dev_iWork,
                                 (cufftComplex *) (self->dev_rWork+ 2*(iproj*self->dim_fft))   ,
				 CUFFT_INVERSE));
    } // end of loop on projections
  }
  
  {  // per 360 2by2
    dim3 blk,grd;
    blk = dim3( blsize_cufft , 1 , 1 );
    grd = dim3( iDivUp(self->num_bins*TWO2by2,blsize_cufft) , 1, 1 );
    if(  self->fai360==0) {
      kern_recopy<<<grd,blk>>>(self->num_bins*TWO2by2,self->dim_fft*TWO2by2,self->nprojs_span, self->dev_Work_perproje,  self->dev_rWork  // 2by2 kernel 
                               );
    } else {
      kern_recopy_slope_2by2<<<grd,blk>>>(self->num_bins*TWO2by2,   self->dim_fft*TWO2by2,
				     self->nprojs_span, self->dev_Work_perproje,  self->dev_rWork,  // 2by2 
				     self->overlapping*TWO2by2, self->pente_zone*TWO2by2, self->flat_zone*TWO2by2,
				     self->d_axis_s,
				     self->prof_shift  , self->prof_fact
				     );
    }
  }

  CUDA_SAFE_CALL( cudaMemcpyToArray((cudaArray*) self->a_cProje_voidptr, 0, 0,self->dev_Work_perproje ,  // 2by2 cProj  allocare, dichiarare anche texture
				    self->nprojs_span*self->num_bins*sizeof(float)*2 , cudaMemcpyDeviceToDevice) );
  
  CUDA_SAFE_CALL( cudaBindTextureToArray(texcProjes,(cudaArray*) self->a_cProje_voidptr) );  //  !! unbind ??   //  2by2  texture texProjes_c 
  texcProjes.filterMode = cudaFilterModeLinear;



  if(self->DZPERPROJ==0.0) {
    dim3      dimBlock         (  16  ,  16   );
    int y = 0;
    int itime=0;
    {
      dim3 dimGrid ( self->NblocchiPerLinea32 ,   self->NblocchiPerColonna32  );
      // dim3 dimGrid ( self->NblocchiPerLinea ,   self->NblocchiPerColonna  );
      if(self->CONICITY_FAN ==0) {
	gputomo_kernel32_2by2<<<dimGrid,dimBlock,256*3*sizeof(float),(((Gpu_Streams*) self->gpu_streams)->stream[itime%2])>>>   // 2by2 bisognera cambiare i kernels
	  (self->nprojs_span,self->num_bins, self->axis_position,
	   (float2*) self->d_SLICE,
	   self->gpu_offset_x, self->gpu_offset_y + y,
	   self->d_cos_s , self->d_sin_s , self->d_axis_s
	   );
      } else  {
	gputomo_kernel32_fan_2by2<<<dimGrid,dimBlock,256*3*sizeof(float),(((Gpu_Streams*) self->gpu_streams)->stream[itime%2])>>>
	  (self->nprojs_span,self->num_bins, self->axis_position,
	   (float2*)self->d_SLICE,
	   self->gpu_offset_x, self->gpu_offset_y + y,
	   self->d_cos_s , self->d_sin_s , self->d_axis_s,  self->FAN_FACTOR,
	   self->SOURCE_X
	   );
      }
    }
  }  else {
    int ipoffset;
    ipoffset =  self->tot_proj_num_list[0] + npj_offset ;

    dim3      dimBlock         (  16  ,  16   );
    int y = 0;
    int itime=0;
    {
      dim3 dimGrid ( self->NblocchiPerLinea32 ,   self->NblocchiPerColonna32  );
      // dim3 dimGrid ( self->NblocchiPerLinea ,   self->NblocchiPerColonna  );


      int2 inizio2, fine2 ;
      float2 fattore_estremi2;
      inizio2.x = inizio[0]; 
      inizio2.y = inizio[1]; 
      fine2.x = fine[0]; 
      fine2.y = fine[1]; 
      fattore_estremi2.x = fattore_estremi[0]; 
      fattore_estremi2.y = fattore_estremi[1]; 
      
      if(self->CONICITY_FAN ==0) {
	gputomo_kernel32_heli_2by2<<<dimGrid,dimBlock,256*3*sizeof(float),(((Gpu_Streams*) self->gpu_streams)->stream[itime%2])>>> // 2by2 utilizzare inizio fine e fattore raddoppiati. fare nuovo kernel
	  (self->nprojs_span,self->num_bins, self->axis_position,
	   (float2*)self->d_SLICE,
	   self->gpu_offset_x, self->gpu_offset_y + y,
	   self->d_cos_s , self->d_sin_s , self->d_axis_s,
	   inizio2 ,fine2,
	   fattore_estremi2,
	   ipoffset
	   );
      } else  {
	gputomo_kernel32_fan_heli_2by2<<<dimGrid,dimBlock,256*3*sizeof(float),(((Gpu_Streams*) self->gpu_streams)->stream[itime%2])>>>
	  (self->nprojs_span,self->num_bins, self->axis_position,
	   (float2*)self->d_SLICE,
	   self->gpu_offset_x, self->gpu_offset_y + y,
	   self->d_cos_s , self->d_sin_s , self->d_axis_s,  self->FAN_FACTOR,
	   self->SOURCE_X,
	   inizio2, fine2,
	   fattore_estremi2,
	   ipoffset
	   );
      }
    }
  }

  int y=0;
  cudaMemcpy2D(
	       // CUDA_SAFE_CALL(
	       // cudaMemcpy2DAsync(
	       SLICE+y*self->num_x,self->num_x*sizeof(float)*TWO2by2, // 2by2
	       self->d_SLICE ,
	       self->dimrecx*sizeof(float)*TWO2by2,
	       self->num_x*sizeof(float)*TWO2by2,
	       self->num_y,
	       cudaMemcpyDeviceToHost
	       // ,
	       //  (((Gpu_Streams*) self->gpu_streams)->stream[itime%2])
	       );
  //  );
  cudaError_t last = cudaGetLastError();
  if(last!=cudaSuccess) {
    printf("memcpy async Last error-reset: %s \n", cudaGetErrorString( last));
    exit(1);
  }


  return 1;
}







#undef blsize_cufft



void  gpu_pagCtxCreate (Gpu_pag_Context * self) {
  if( self->gpuctx == NULL ) {

    cuInit(0);

    self->gpuctx = (void*)  malloc(sizeof(CUcontext)) ;
    // cudaSetDeviceFlags(    cudaDeviceMapHost   ) ;
    cudaSetDevice(self->MYGPU);
    
    cuCtxCreate( (CUcontext *) self->gpuctx   ,
		 // CU_CTX_SCHED_YIELD*0,
		 CU_CTX_SCHED_SPIN,
		 self->MYGPU
		 );
  }
}

void  gpu_pagCtxDestroy(Gpu_pag_Context * self) {
  cuCtxSetCurrent ( *((CUcontext *) self->gpuctx  ))  ;
  cuCtxDestroy( *((CUcontext *) (self->gpuctx))   );
}

void  gpu_pag( Gpu_pag_Context *   self , float * auxbuffer )   {
  char messaggio[1000];
  int dimbuff = self->size_pa0*self->size_pa1;

  cuCtxSetCurrent ( *((CUcontext *) self->gpuctx  ))  ;


  CUDA_SAFE_CALL(cudaMemcpy(self->d_fftwork,auxbuffer,
			    sizeof(cufftComplex)*dimbuff,
			    cudaMemcpyHostToDevice));



  CUFFT_SAFE_CALL( cufftExecC2C(*((cufftHandle*)self->FFTplan_ptr),
				(cufftComplex *) self->d_fftwork,
				(cufftComplex *) self->d_fftwork,
				CUFFT_FORWARD) , "doing a   cufftExecC2C"  );


  {
    dim3 blk,grd;
    int blksz = 216;
    blk = dim3(blksz  , 1 , 1 );
    int done=0;
    int todo=0;
    while(done<dimbuff) {
      //      if(done) printf("WARNING by pieces line %d \n", __LINE__);

      int nb =   min(  iDivUp(dimbuff,blksz ), 0xFFFF )  ;
      todo = min( nb* blksz , dimbuff-done   ) ;
      grd = dim3( nb , 1, 1 );

      kern_mult<<<grd,blk>>>( ((cufftComplex *) self->d_fftwork)+done,
			      ((cufftComplex *) self->d_kernelbuffer)+done,
			      todo );
      done+=todo;
    }
  }


  CUFFT_SAFE_CALL( cufftExecC2C(*((cufftHandle*)self->FFTplan_ptr),
				(cufftComplex *) self->d_fftwork,
				(cufftComplex *) self->d_fftwork,
				CUFFT_INVERSE), " doing CUFFT_INVERSE"  );

  CUDA_SAFE_CALL(cudaMemcpy(auxbuffer,self->d_fftwork,
			    sizeof(cufftComplex)*dimbuff,
			    cudaMemcpyDeviceToHost));
}


void *  AllocPinned(Gpu_Context * self, size_t size ) {
  void *res;
  cuCtxSetCurrent ( *((CUcontext *) self->gpuctx  ))  ;
  // CUDA_SAFE_CALL(
  cuMemHostAlloc(&res,size ,CU_MEMHOSTALLOC_WRITECOMBINED || CU_MEMHOSTALLOC_DEVICEMAP ); // CU_MEMHOSTALLOC_PORTABLE    );
  //);

  // CUDA_SAFE_CALL(cudaHostAlloc(&res,size ,cudaHostAllocPortable  ););

  // cudaHostAlloc(&res,size ,cudaHostAllocDefault  );
  printf(" memoria %p \n", res );
  return res;
}

void FreePinned(Gpu_Context * self,void *ptr) {
  cuCtxSetCurrent ( *((CUcontext *) self->gpuctx  ))  ;
  cudaFreeHost(ptr);
}

void gpu_pagFree( Gpu_pag_Context *   self  )  {
  char messaggio[1000];
  cuCtxSetCurrent ( *((CUcontext *) self->gpuctx  ))  ;

  
  CUDA_SAFE_CALL(cudaFree(self->d_fftwork) );
  CUDA_SAFE_CALL(cudaFree(self->d_kernelbuffer) );
  CUFFT_SAFE_CALL (cufftDestroy(*((cufftHandle*)self->FFTplan_ptr)), "doing cufftDestroy ");
  delete ((cufftHandle*)self->FFTplan_ptr ) ;
};



void gpu_pagInit( Gpu_pag_Context *   self  )  {
  char messaggio[1000];
  cuCtxSetCurrent ( *((CUcontext *) self->gpuctx  ))  ;
  int dimbuff = self->size_pa0*self->size_pa1;
  self->FFTplan_ptr = (void * )  new cufftHandle;
  CUDA_SAFE_CALL(cudaMalloc((void**)&(self->d_fftwork),
			    sizeof(cufftComplex)*dimbuff));
  CUFFT_SAFE_CALL( cufftPlan2d( (cufftHandle*)   self->FFTplan_ptr,
				self->size_pa0 ,self->size_pa1  ,
				CUFFT_C2C ), " doing a cufftPlan2d  " );
  CUDA_SAFE_CALL(cudaMalloc((void**)&(self->d_kernelbuffer),
			    sizeof(cufftComplex)*dimbuff));
  CUDA_SAFE_CALL(cudaMemcpy(self->d_kernelbuffer,self->kernelbuffer,
			    sizeof(cufftComplex)*dimbuff,
			    cudaMemcpyHostToDevice));
};


// ============================================================================================================


texture<float,2> Img_texture;
texture<float,2> Der0_texture;
texture<float,2> Der1_texture;

#define blksizeX 16
#define blksizeY 16


__global__ static void     grad_kernel (int dim0, int  dim1, int pitch,  float *deri_0 ,  float *deri_1) {
  const int tidx = threadIdx.x;
  const int bidx = blockIdx.x;
  const int tidy = threadIdx.y;
  const int bidy = blockIdx.y;
  const int J =  bidx*blksizeX +  tidx;
  const int II =  bidy*blksizeY +  tidy;


  if(II<dim0 ) {
    if(J<dim1-1) {
      ((float *) (((char *)deri_1)  + II*pitch ))[ J ]  =tex2D( Img_texture, J+1.5f, II+0.5f)- tex2D( Img_texture, J+0.5f, II+0.5f)  ;
    } else {
      ((float *) (((char *)deri_1)  + II*pitch ))[ J ]  = 0.0f ;
    }
  }
  if(J<dim1 ) {
    if(II<dim0-1) {
      ((float *) (((char *)deri_0)  + II*pitch ))[ J ]  =tex2D( Img_texture, J+0.5f, II+1.5f)- tex2D( Img_texture, J+0.5f, II+0.5f)  ;
    } else {
      ((float *) (((char *)deri_0)  + II*pitch ))[ J ]  = 0.0f ;
    }
  }
};

__global__ static void      normgrad_kernel ( int dim0, int  dim1, int pitch, float *normgrad){
  const int tidx = threadIdx.x;
  const int bidx = blockIdx.x;
  const int tidy = threadIdx.y;
  const int bidy = blockIdx.y;

  const int II =  bidy*blksizeY +  tidy;
  const int J =  bidx*blksizeX +  tidx;
  float d0,d1;
  if(II<dim0 ) {
    if(J<dim1-1) {
      d0=tex2D( Img_texture, J+1.5f, II+0.5f)- tex2D( Img_texture, J+0.5f, II+0.5f)  ;
    } else {
      d0 = 0.0f ;
    }
  }
  if(J<dim1 ) {
    if(II<dim0-1) {
      d1=tex2D( Img_texture, J+0.5f, II+1.5f)- tex2D( Img_texture, J+0.5f, II+0.5f)  ;
    } else {
      d1= 0.0f ;
    }
  }
  if (II < dim0 && J<dim1) ((float *) (((char *)normgrad)  + II*pitch ))[ J ]  = sqrt( d0*d0+d1*d1  );
}

__global__ static void     div_kernel (int dim0, int  dim1, int pitch, float *div, float w) {
  const int tidx = threadIdx.x;
  const int bidx = blockIdx.x;
  const int tidy = threadIdx.y;
  const int bidy = blockIdx.y;

  const int II =  bidy*blksizeY +  tidy;
  const int J =  bidx*blksizeX +  tidx;

  float res=0.0f;

  if(II<dim0  && J<dim1 ) {
    res  = tex2D( Der0_texture, J+0.5f, II+0.5f)+ tex2D( Der1_texture, J+0.5f, II+0.5f)  ;
  }

  if(II>0 ) {
    res -= tex2D( Der0_texture, J+0.5f, II-0.5f) ;
  }
  if(J>0 ) {
    res -= tex2D( Der1_texture, J-0.5f, II+0.5f) ;
  }
  if (II<dim0 && J<dim1) ((float *) (((char *)div)  + II*pitch ))[ J ]  = res*w ;
};

__global__ static void   projdual_kernel (int dim0, int  dim1, int pitch,
					  float *grad_tmp0 , float *grad_tmp1 ,
					  float *deri_0, float * deri_1  ) {
  const int tidx = threadIdx.x;
  const int bidx = blockIdx.x;
  const int tidy = threadIdx.y;
  const int bidy = blockIdx.y;

  const int II =  bidy*blksizeY +  tidy;
  const int J =  bidx*blksizeX +  tidx;

  float norm=0.0f,d0,d1;
  if(II<dim0  && J<dim1 ) {
    d0=((float *) (((char *)deri_0)  + II*pitch ))[ J ];
    d1=((float *) (((char *)deri_1)  + II*pitch ))[ J ];
    norm = max( sqrt(d0*d0+d1*d1),1.0f);
    ((float *) (((char *)grad_tmp0)  + II*pitch ))[ J ] = d0/norm;
    ((float *) (((char *)grad_tmp1)  + II*pitch ))[ J ] = d1/norm;
  }
}

__global__ static void   linearcomb_kernel (int dim0, int  dim1, int pitch,
					    float *res , float *va ,float a,
					    float *vb, float b  ) {
  const int tidx = threadIdx.x;
  const int bidx = blockIdx.x;
  const int tidy = threadIdx.y;
  const int bidy = blockIdx.y;

  const int II =  bidy*blksizeY +  tidy;
  const int J =  bidx*blksizeX +  tidx;

  if(II<dim0  && J<dim1 ) {
    ((float *) (((char *)res)  + II*pitch ))[ J ] =
      ((float *) (((char *)va)  + II*pitch ))[ J ] *a
      +
      ((float *) (((char *)vb)  + II*pitch ))[ J ] *b
      ;
  }
}



float dual_gap(dim3 &dimGrid, dim3 &dimBlock,
	       int dim0, int dim1,
	       float  im_norm,
	       float *new_,
	       float *gap,
	       float weight,
	       float *normsgrad,
	       int pitch_img,
	       cudaChannelFormatDesc &ch_desc) {

  CUDA_SAFE_CALL( cudaBindTexture2D( NULL,Img_texture ,new_ , ch_desc, dim0, dim1,pitch_img )) ;
  normgrad_kernel<<<dimGrid,dimBlock>>> (dim0, dim1,pitch_img, normsgrad  );

  float normgrad =  cublasSasum (  dim0* pitch_img/sizeof(float) ,normsgrad , 1);
  float tv_new = 2 * weight * normgrad ;
  // float norm2gap =  cublasSdot ( dim0* pitch_img/sizeof(float) ,   gap   , 1 , gap , 1); ;

  float norm2gap =   cublasSnrm2( dim0* pitch_img/sizeof(float) ,   gap   , 1 );
  norm2gap *=norm2gap;

  // float norm2new_ =  cublasSdot ( dim0* pitch_img/sizeof(float) ,   new_   , 1 , new_ , 1); ;
  float norm2new_ =  cublasSnrm2 ( dim0* pitch_img/sizeof(float) ,   new_   , 1 ); ;
  norm2new_*=norm2new_;

  float dual_gap =  norm2gap + tv_new - im_norm + norm2new_ ;
  return 0.5f / im_norm * dual_gap;
}

float probe(float *d, int pdim ) {
  return cublasSdot (pdim,   d   , 1 , d  , 1);
}

// ===========================================================================================================


void put_patches( float *pimg, float *imgA,int dim0, int dim1, int s0, int s1,
		  int end0, int end1, int dim_patches, int direction,
		  float *paverages, float *dists, float *freqs, int vectoriality) {

  int doppio=vectoriality;
  float *img_s[] ={imgA, imgA+ dim0*dim1 , NULL };
  int pos=0;
  int iwidget=0;
  if(direction==1) {
    for(int i0=0; i0<end0; i0++) {
      for(int i1=0; i1<end1; i1++) {

	for(int idop =0; idop<doppio; idop++) {
	  float *img = img_s[idop];
	  paverages[iwidget] = 0;
	  for(int pi0= 0; pi0<dim_patches; pi0++) {
	    for(int pi1= 0; pi1<dim_patches; pi1++) {
	      pimg[pos] = img[ (s0+ i0*dim_patches + pi0 )*dim1  +s1  + i1*dim_patches + pi1   ];
	      paverages[iwidget]+= pimg[pos] ;
	      pos++;
	    }
	  }
	  paverages[iwidget] /= (dim_patches)*(dim_patches);
	  for(int i=pos-(dim_patches)*(dim_patches); i<pos; i++) pimg[i] -= paverages[iwidget]  ;
	  iwidget++;
	}
      }
    }
  } else {
    float C = 0.5f*(dim_patches-1), d,d0;
    int address ;
    for(int i0=0 ; i0<end0; i0++) {
      for(int i1=0 ; i1<end1; i1++) {

	for(int idop =0; idop<doppio; idop++) {
	  float *img = img_s[idop];
	  for(int pi0= 0; pi0<dim_patches; pi0++) {
	    d0=fabs(pi0-C);
	    for(int pi1= 0; pi1<dim_patches; pi1++) {
	      d = d0 + fabs(pi1-C);
	      address =   ( s0 + i0*dim_patches + pi0 )*dim1  + s1  + i1*dim_patches + pi1 ;
	      if(d< dists[ address + idop*dim0*dim1 ] ) {
		img[ address  ]= pimg[pos] + paverages[iwidget]  ;
		dists[ address + idop*dim0*dim1]=d;
		freqs[ address + idop*dim0*dim1]=1;
	      } else if ( d == dists[ address + idop*dim0*dim1 ]  ) {
		img[ address  ]= ((pimg[pos]+paverages[iwidget]) + img[ address]*freqs[ address+ idop*dim0*dim1 ] )/(  freqs[ address+ idop*dim0*dim1 ]+1 ) ;
		freqs[ address+ idop*dim0*dim1 ] +=1 ;
	      }
	      pos++;
	    }
	  }
	  iwidget++;
	}
      }
    }
  }
}






void put_patches( float *pimg, float *img, int dim1, int s0, int s1,
		  int end0, int end1, int dim_patches, int direction,
		  float *paverages, float *dists, float *freqs) {
  int pos=0;
  int iwidget=0;
  if(direction==1) {
    //
    for(int i0=0; i0<end0; i0++) {
      for(int i1=0; i1<end1; i1++) {
	paverages[iwidget] = 0;
	for(int pi0= 0; pi0<dim_patches; pi0++) {
	  for(int pi1= 0; pi1<dim_patches; pi1++) {
	    pimg[pos] = img[ (s0+ i0*dim_patches + pi0 )*dim1  +s1  + i1*dim_patches + pi1   ];
	    paverages[iwidget]+= pimg[pos] ;
	    pos++;
	  }
	}
	paverages[iwidget] /= (dim_patches)*(dim_patches);
	for(int i=pos-(dim_patches)*(dim_patches); i<pos; i++) pimg[i] -= paverages[iwidget]  ;
	iwidget++;
      }
    }
  } else {
    float C = 0.5f*(dim_patches-1), d,d0;
    int address ;
    for(int i0=0 ; i0<end0; i0++) {
      for(int i1=0 ; i1<end1; i1++) {
	for(int pi0= 0; pi0<dim_patches; pi0++) {
	  d0=fabs(pi0-C);
	  for(int pi1= 0; pi1<dim_patches; pi1++) {
	    d = d0 + fabs(pi1-C);
	    address =   ( s0 + i0*dim_patches + pi0 )*dim1  + s1  + i1*dim_patches + pi1 ;
	    if(d< dists[ address ] ) {
	      img[ address  ]= pimg[pos] + paverages[iwidget]  ;
	      dists[ address ]=d;
	      freqs[ address ]=1;
	    } else if ( d == dists[ address ]  ) {
	      img[ address  ]= ((pimg[pos]+paverages[iwidget]) + img[ address]*freqs[ address ] )/(  freqs[ address ]+1 ) ;
	      freqs[ address ] +=1 ;
	    }
	    pos++;
	  }
	}
	iwidget++;
      }
    }
  }
}



__global__  static  void kern_shrink(int nitems, int ncomps,float *d_w,  float *d_dw,  float  weight, float *d_notzero ){
  int gid;
  float w0,w1,tmp,  notzero;

  gid = threadIdx.x + blockIdx.x*blockDim.x;
  // int iwidget = gid/ncomps  ;

  if(gid<nitems*ncomps) {

    // ss = d_ss[iwidget];

    w0 = d_w[gid];
    tmp = w0+d_dw[gid];
    // w1 = copysignf( max(fabs(tmp)-ss*weight,0.0f) , tmp) ;
    w1 = copysignf( max(fabs(tmp)-weight,0.0f) , tmp) ;
    d_w [gid] = w1;
    notzero=0.0f;

    if(w1!=0.0f) {
      notzero = 1.0f;
    }

    d_dw[gid] = w1 - w0;
    d_notzero[gid]=notzero;

  }
}



// _global__  static  void  kern_omp_0( float *d_sc, float *d_cc, int nwidget, int pitchpatch, float *d_w) {
//   int gid;
//   gid = threadIdx.x + blockIdx.x*blockDim.x;
//   if(gid<nwidget) {
//     d_w[0*pitchpatch+gid] =  d_sc[0*pitchpatch+gid]/d_cc[(0*1+0)*pitchpatch+gid] ;
//   }
// }


template<int n_pursuit>
__global__  static  void  kern_omp( float * d_sc, float *d_cc, int nwidget, int pitchpatch, float *d_w, float *d_ss, float tol) {
  int gid;
  gid = threadIdx.x + blockIdx.x*blockDim.x;
  float v[n_pursuit][n_pursuit], scal, coeffs[n_pursuit];
  int  i,j,k,l;
  v[0][0]=1.0f;
  if(gid<nwidget) {
    for(k=0; k<n_pursuit; k++) {
      coeffs[k]=0.0f;
    }
    for(i=1; i<n_pursuit; i++) {
      if( fabs(d_sc[i*pitchpatch+gid]) > tol* d_ss[gid]) {
	for(j=0; j<n_pursuit; j++) {
	  v[i][j]=0.0f;
	}
	v[i][i]=1.0;
	for(j=0; j<i; j++) {
	  scal=0.0f;
	  for(k=0; k<=j; k++) {
	    for(l=0; l<=i; l++) {
	      scal += v[i][l] *v[j][k] *   d_cc[ (k*n_pursuit+l)*pitchpatch+gid ] ;
	    }
	  }
	  for(k=0; k<=j; k++) {
	    v[i][k] -=  scal *v[j][k] ;
	  }
	  scal=0.0f;
	  for(k=0; k<=i; k++) {
	    for(l=0; l<=i; l++) {
	      scal += v[i][l] *v[i][k] *   d_cc[ (k*n_pursuit+l)*pitchpatch+gid ] ;
	    }
	  }
	  scal=1.0/sqrt(scal);
	  for(k=0; k<=i; k++) {
	    v[i][k] =  scal *v[i][k] ;
	  }
	}
      }
    }
    for(i=0; i<n_pursuit; i++) {
      if( fabs(d_sc[i*pitchpatch+gid]) > tol* d_ss[gid]) {
	scal=0.0f;
	for(k=0; k<=i; k++) {
	  scal+= v[i][k] * d_sc[k*pitchpatch+gid];
	}
	for(k=0; k<=i; k++) {
	  coeffs[k]+= v[i][k] * scal;
	}
      }
    }
    for(k=0; k<n_pursuit; k++)  d_w[k*pitchpatch+gid] =  coeffs[k] ;
  }
}







float  tv_denoising_patches_this_L1(float * d_y,float * d_R, float *d_X,float * d_wold, float *d_w ,float *d_dw ,
				    int nwidget, int n_comps, int sizepatch, float *pimg  , float weight,
				    float Lipschitz, float *d_notzero, float *d_ss, float *ftmps, int N_ITERS_DENOISING
				    ) {


  CUDA_SAFE_CALL(cudaMemcpy ( d_y,  pimg ,  nwidget*sizepatch*sizeof(float)   ,cudaMemcpyHostToDevice  ));
  CUDA_SAFE_CALL(cudaMemcpy ( d_R,  d_y  ,  nwidget*sizepatch*sizeof(float)   ,cudaMemcpyDeviceToDevice  ));
  CUDA_SAFE_CALL( cudaMemset( d_w,  0    ,  nwidget*n_comps*sizeof(float)    ) )    ;
  CUDA_SAFE_CALL( cudaMemset( d_wold,  0    ,  nwidget*n_comps*sizeof(float)    ) ) ;


  float tol=1.0e-10 ;
  float rtol =  cublasSnrm2(  nwidget*sizepatch  ,  d_y    , 1);
  rtol = rtol*rtol*tol;

  int maxiter=N_ITERS_DENOISING;
  float res=0.0f;

  float t=1.0f;

  {
    int iw, i;
    float tmp;
    for(iw=0; iw<nwidget; iw++) {
      ftmps[iw]=0.0f;
      for(i=0; i<sizepatch; i++) {
	tmp =  pimg[iw*sizepatch+i] ;
	ftmps[iw] +=   tmp*tmp ;
      }
      ftmps[iw]=sqrt(ftmps[iw]);
    }
    CUDA_SAFE_CALL(cudaMemcpy (d_ss, ftmps,  nwidget*sizeof(float)   ,cudaMemcpyHostToDevice  ));
  }



  for(int n_iter=0; n_iter<maxiter; n_iter++) {

    float alpha= 1.0f/Lipschitz;
    float beta = 0.0f;

    cublasSgemm('T','N', nwidget, n_comps ,  sizepatch ,
		alpha,
		d_R    , sizepatch ,
		d_X    , sizepatch  ,
		beta,
		d_dw  , nwidget );

    //AMMENDA
    if(1){
      dim3 blk,grd;
      blk = dim3( WKSIZE , 1 , 1 );
      grd = dim3( iDivUp( nwidget*n_comps  ,WKSIZE) , 1, 1 );
      // if(n_iter%100 ==0) t=1.0f;
      float told = t;
      t=(1.0f+sqrt(1.0f+4.0f*t*t))/2.0f;
      kern_shrink_fista<<<grd,blk>>>(nwidget,n_comps ,  d_w,    d_dw , d_wold, weight/Lipschitz, d_notzero, told, t);
    } else {
      dim3 blk,grd;
      blk = dim3( WKSIZE , 1 , 1 );
      grd = dim3( iDivUp( nwidget*n_comps  ,WKSIZE) , 1, 1 );
      kern_shrink<<<grd,blk>>>(nwidget, n_comps ,  d_w,    d_dw ,  weight/Lipschitz, d_notzero);
    }

    alpha=-1.0f;
    beta = 1.0f;

    cublasSgemm('N','T',sizepatch ,nwidget ,n_comps,
		alpha,
		d_X , sizepatch,
		d_dw, nwidget,
		beta,
		d_R  , sizepatch );



    float wmax  = 0.0f;
    float dwmax = 0.0f;
    int imax = cublasIsamax(nwidget*n_comps,d_dw ,1 );
    CUDA_SAFE_CALL(cudaMemcpy  (  &dwmax,  d_dw+imax-1,  1*sizeof(float)   ,cudaMemcpyDeviceToHost  ));
    imax = cublasIsamax(nwidget*n_comps,d_w ,1 );
    CUDA_SAFE_CALL(cudaMemcpy  (  &wmax,  d_w+imax-1,  1*sizeof(float)   ,cudaMemcpyDeviceToHost  ));

    dwmax=fabs(dwmax);
    wmax=fabs(wmax);

    float notzero = cublasSasum (nwidget*n_comps ,d_notzero , 1);
    if((n_iter+1)%100==0) printf(" SPARSITA %e \n", notzero/(nwidget*n_comps));

    res = cublasSasum (nwidget*n_comps ,d_w , 1)*weight;
    float norma =  cublasSnrm2( nwidget*sizepatch  ,  d_R    , 1);
    res=res+norma*norma/2.0f;
    if((n_iter+1)%100==0) printf("n_iter %d dwmax/wmax, wmax  dwmax %e %e %e  error %e \n", n_iter,  dwmax/wmax, wmax, dwmax, res);
    // if(wmax==0.0f || dwmax/wmax < tol) break;
    if(n_iter==maxiter-1) printf(" attenzione patches denoising esce prima convergenza\n");

  }
  float alpha= -1.0f;
  cublasSaxpy ( sizepatch*nwidget ,  alpha, d_R, 1, d_y, 1);
  {
    float alpha= 1.0f;
    float beta = 0.0f;
    cublasSgemm('N','T',sizepatch ,nwidget ,n_comps,
		alpha,
		d_X , sizepatch,
		d_w, nwidget,
		beta,
		d_y  , sizepatch );
  }


  {
    for(int i=0; i<nwidget*sizepatch ; i++) pimg[i]=0.0;
  }

  CUDA_SAFE_CALL(cudaMemcpy  ( pimg , d_y ,  nwidget*sizepatch*sizeof(float)   ,cudaMemcpyDeviceToHost  ));
  return res;

}





float  tv_denoising_patches_L1(Gpu_Context * self,int dim0,int dim1,float  *img,float  *imgresultA , float weight,
			       float *patches, int n_comps, int dim_patches, int N_ITERS_DENOISING,
			       int vectoriality )  {
  int doppio= vectoriality;

  int np0    = (dim0/dim_patches);
  int np1    = (dim1/dim_patches);
  // int marge0 = np0*dim_patches;
  // int marge1 = np1*dim_patches;

  float * pimg = (float *) malloc( dim0*dim1*doppio*sizeof(float));
  float * paverages  = (float *) malloc( (np0+1)*(np1+1)*doppio*sizeof(float));
  float *d_y, *d_R, *d_X, *d_tmp, *d_w, *d_dw, *d_ss, *d_notzero;
  float res=0.0f;

  float * dists = (float *) malloc( doppio*dim0*dim1*sizeof(float));
  float * freqs = (float *) malloc( doppio*dim0*dim1*sizeof(float));
  float *ftmps;
  int sizepatch = dim_patches*dim_patches*doppio;


  cuCtxSetCurrent ( *((CUcontext *) self->gpuctx  ))  ;


  float *imgresult;
  if(img!=imgresultA) {
    imgresult=imgresultA ;
  } else {
    imgresult = (float *) malloc( doppio*dim1*dim0*sizeof(float)   ) ;
  }

  memcpy(imgresult, img, doppio*dim1*dim0*sizeof(float) );

  ftmps = (float *) malloc( doppio*dim_patches*dim_patches*(np0+1)*(np1+1)*sizeof(float) );



  CUDA_SAFE_CALL( cudaMalloc(   &d_R,  dim0*dim1*doppio*sizeof(float)    ) )    ;
  CUDA_SAFE_CALL( cudaMalloc(   &d_y,  dim0*dim1*doppio*sizeof(float)    ) )    ;
  CUDA_SAFE_CALL( cudaMalloc(   &d_X,  n_comps*dim_patches*dim_patches*doppio*sizeof(float)    ) )  ;
  CUDA_SAFE_CALL( cudaMalloc(   &d_tmp,  (np0+1)*(np1+1)*n_comps*sizeof(float)    ) )   ;

  CUDA_SAFE_CALL( cudaMalloc(   &d_w,  (np0+1)*(np1+1)*n_comps*sizeof(float)    ) ) ;
  CUDA_SAFE_CALL( cudaMalloc(   &d_dw,  (np0+1)*(np1+1)*n_comps*sizeof(float)    ) )    ;
  CUDA_SAFE_CALL( cudaMalloc(   &d_ss,  (np0+1)*(np1+1)*sizeof(float)    ) )    ;
  CUDA_SAFE_CALL( cudaMalloc(   &d_notzero,  (np0+1)*(np1+1)*n_comps*sizeof(float)    ) )   ;

  CUDA_SAFE_CALL(cudaMemcpy (   d_X , patches , n_comps*dim_patches*dim_patches*doppio*sizeof(float)   ,cudaMemcpyHostToDevice  ));

  static float Lipschitz=0.0;

  if( Lipschitz==0.0)
    {

      float v0[n_comps];
      for(int i=0; i<n_comps; i++) {
	v0[i]=0.0f;
      }
      v0[0]=1;
      CUDA_SAFE_CALL(cudaMemcpy (   d_w , v0 , n_comps*sizeof(float)   ,cudaMemcpyHostToDevice  ));



      for(int ivolta=0; ivolta<100; ivolta++) {

	float alpha= 1.0f;
	float beta = 0.0f;

	cublasSgemm('N','N',sizepatch ,1 ,n_comps,
		    alpha,
		    d_X  , sizepatch,
		    d_w, n_comps ,
		    beta,
		    d_dw  , sizepatch );

	cublasSgemm('T','N',n_comps ,1 ,sizepatch,
		    alpha,
		    d_X  , sizepatch,
		    d_dw, sizepatch ,
		    beta,
		    d_w  , n_comps );
	float norma =  cublasSnrm2( n_comps  ,  d_w    , 1);
	printf("ivolta %d  norma %e\n", ivolta, norma);
	cublasSscal(n_comps, 1.0f/norma , d_w, 1) ;
	Lipschitz=1.1*norma;
      }


    }
  {
    memset(freqs, 0,  doppio*dim0*dim1*sizeof(float)  );
    int i;
    for(i=0; i< dim0*dim1*doppio; i++) {
      dists[i]=1.0e10;
    }
    int shift0, shift1;
    for( shift0=0; shift0<dim_patches ; shift0+= self->STEPFORPATCHES) {
      for( shift1=0; shift1<dim_patches ; shift1+= self->STEPFORPATCHES) {
	int nw0    = ((dim0-shift0)/dim_patches);
	int nw1    = ((dim1-shift1)/dim_patches);

        // printf(" QUI tol --   %d %d\n",  self->STEPFORPATCHES,dim_patches );

	put_patches(pimg,img, dim0, dim1, shift0,shift1,nw0,nw1, dim_patches ,1 , paverages, dists, freqs, vectoriality);
	// put_patches(pimg,img, dim1, shift0,shift1,nw0,nw1, dim_patches ,1 , paverages, dists, freqs);

	res+=tv_denoising_patches_this_L1( d_y, d_R, d_X, d_tmp, d_w , d_dw,nw0*nw1 , n_comps,sizepatch , pimg ,
					   weight ,Lipschitz, d_notzero, d_ss, ftmps ,N_ITERS_DENOISING);

	put_patches(pimg,imgresult, dim0, dim1, shift0,shift1,nw0,nw1,  dim_patches ,-1 , paverages, dists, freqs, vectoriality);
	// put_patches(pimg,imgresult,  dim1, shift0,shift1,nw0,nw1,  dim_patches ,-1 , paverages, dists, freqs);
      }
    }

  }

  if(img!=imgresultA) {
  } else {
    memcpy( imgresultA ,imgresult , doppio*dim1*dim0*sizeof(float) );
    free(imgresult);
  }


  free(dists);
  free(freqs);
  free(pimg);
  free(paverages);
  free(ftmps);
  CUDA_SAFE_CALL(cudaFree(d_R  ));
  CUDA_SAFE_CALL(cudaFree(d_y  ));
  CUDA_SAFE_CALL(cudaFree(d_X  ));
  CUDA_SAFE_CALL(cudaFree(d_tmp));
  CUDA_SAFE_CALL(cudaFree(d_w  ));
  CUDA_SAFE_CALL(cudaFree(d_dw ));
  CUDA_SAFE_CALL(cudaFree(d_notzero ));
  CUDA_SAFE_CALL(cudaFree(d_ss ));

  return res;
}



void   tv_denoising_patches_this_OMP(float * d_y,float * d_R, float *d_X, float *comps,float *d_w ,float *d_dw ,int nwidget, int n_comps,int dim_patches,
				     float *pimg  ,
				     float * comp_scalprods, float *cs_scalprods,
				     float **d_vects, float *ftmps, float *ftmps2, int *choosedcomps, int npursuit,
				     float *d_ss,float *d_sc, float *d_cc, int pitchpatch, float *cr_scalprods,
				     float tol) {


  int sizepatch = dim_patches*dim_patches;

  CUDA_SAFE_CALL(cudaMemcpy ( d_y,  pimg ,  nwidget*sizepatch*sizeof(float)   ,cudaMemcpyHostToDevice  ));
  CUDA_SAFE_CALL(cudaMemcpy ( d_R,  d_y  ,  nwidget*sizepatch*sizeof(float)   ,cudaMemcpyDeviceToDevice  ));
  CUDA_SAFE_CALL( cudaMemset( d_w,  0    ,  npursuit*pitchpatch*sizeof(float)    ) )    ;
  {
    float alpha=1.0, beta=0.0;
    cublasSgemm('T','N',  n_comps , nwidget,  sizepatch ,
		alpha,
		d_X    , sizepatch  ,
		d_R    , sizepatch ,
		beta,
		d_dw  , n_comps );
    CUDA_SAFE_CALL(cudaMemcpy (cs_scalprods , d_dw   ,  nwidget*n_comps*sizeof(float)   ,cudaMemcpyDeviceToHost  ));
  }


  {
    int iw, i;
    float tmp;
    for(iw=0; iw<nwidget; iw++) {
      ftmps[iw]=0.0f;
      for(i=0; i<sizepatch; i++) {
	tmp =  pimg[iw*sizepatch+i] ;
	ftmps[iw] +=   tmp*tmp ;
      }
      ftmps[iw]=sqrt(ftmps[iw]);
    }
    CUDA_SAFE_CALL(cudaMemcpy (d_ss, ftmps,  nwidget*sizeof(float)   ,cudaMemcpyHostToDevice  ));
  }
  int iw, ic;
  int  imax;
  float wmax,tmp;
  int i_pursuit;

  for(i_pursuit=0;i_pursuit<npursuit; i_pursuit++)   {
    {
      float alpha=1.0, beta=0.0;
      cublasSgemm('T','N',  n_comps , nwidget,  sizepatch ,
		  alpha,
		  d_X    , sizepatch  ,
		  d_R    , sizepatch ,
		  beta,
		  d_dw  , n_comps );
    }

    CUDA_SAFE_CALL(cudaMemcpy (cr_scalprods , d_dw   ,  nwidget*n_comps*sizeof(float)   ,cudaMemcpyDeviceToHost  ));

    for(iw=0; iw<nwidget; iw++) {
      for(ic=0; ic<n_comps; ic++) {
	if(cr_scalprods[iw*n_comps+ic  ]==0.0f ) cr_scalprods[iw*n_comps+ic  ]=1.0e-32;
      }
    }

    for(iw=0; iw<nwidget; iw++) {
      // annullo i precedenti
      for(ic=0; ic<i_pursuit; ic++)  cr_scalprods[iw*n_comps+  choosedcomps[ iw*npursuit +ic ] ]=0.0f ;
    }


    for(iw=0; iw<nwidget; iw++) {
      imax=0;
      wmax =  fabs(cr_scalprods[iw*n_comps+0  ]) ;
      for(ic=1; ic<n_comps; ic++) {
	tmp = fabs(cr_scalprods[iw*n_comps+ic  ]) ;
	if(   tmp>wmax )  {
	  wmax=tmp;
	  imax=ic ;
	}
      }
      ftmps2[iw ] = cs_scalprods[iw*n_comps+imax  ];
      choosedcomps[ iw*npursuit +i_pursuit ] = imax ;
      memcpy(ftmps +  iw*sizepatch, comps +imax* sizepatch   ,  sizepatch*sizeof(float));
      // cudaMemcpy( d_vects[i_pursuit] + iw*sizepatch,  d_X+imax* sizepatch      , sizepatch*sizeof(float) , cudaMemcpyDeviceToDevice ) ;
    }
    cudaMemcpy( d_vects[i_pursuit] ,  ftmps    ,nwidget* sizepatch*sizeof(float) , cudaMemcpyHostToDevice ) ;


    cudaMemcpy( d_sc + i_pursuit*pitchpatch,   ftmps2    , nwidget*sizeof(float) , cudaMemcpyHostToDevice ) ;
    {
      int ic1, ic2, ip1, ip2;
      for(ic1=0; ic1<=i_pursuit; ic1++) {
	for(ic2=0; ic2<=i_pursuit; ic2++) {
	  for(iw=0; iw<nwidget; iw++) {
	    ip1 =  choosedcomps[ iw*npursuit +ic1 ]  ;
	    ip2 =  choosedcomps[ iw*npursuit +ic2 ]  ;
	    ftmps[ iw   ]  =   comp_scalprods[ ip1*n_comps+ip2]   ;
	  }
	  cudaMemcpy( d_cc + (ic1*(i_pursuit+1)+ic2)*pitchpatch  , ftmps   ,  nwidget*sizeof(float)  ,  cudaMemcpyHostToDevice) ;
	}
      }
    }

    {
      dim3 blk,grd;
      blk = dim3( 256 , 1 , 1 );
      grd = dim3( iDivUp( nwidget  ,256) , 1, 1 );

      // printf(" la tolleranza est %e \n", tol);

      if( i_pursuit==0)    kern_omp<1><<<grd,blk>>>( d_sc,d_cc, nwidget, pitchpatch, d_w,d_ss,tol);
      if( i_pursuit==1)    kern_omp<2><<<grd,blk>>>( d_sc,d_cc, nwidget, pitchpatch, d_w,d_ss,tol);
      if( i_pursuit==2)    kern_omp<3><<<grd,blk>>>( d_sc,d_cc, nwidget, pitchpatch, d_w,d_ss,tol);
      if( i_pursuit==3)    kern_omp<4><<<grd,blk>>>( d_sc,d_cc, nwidget, pitchpatch, d_w,d_ss,tol);
      if( i_pursuit==4)    kern_omp<5><<<grd,blk>>>( d_sc,d_cc, nwidget, pitchpatch, d_w,d_ss,tol);
      if( i_pursuit==5)    kern_omp<6><<<grd,blk>>>( d_sc,d_cc, nwidget, pitchpatch, d_w,d_ss,tol);
      if( i_pursuit==6)    kern_omp<7><<<grd,blk>>>( d_sc,d_cc, nwidget, pitchpatch, d_w,d_ss,tol);
      if( i_pursuit==7)    kern_omp<8><<<grd,blk>>>( d_sc,d_cc, nwidget, pitchpatch, d_w,d_ss,tol);
      if( i_pursuit==8)    kern_omp<9><<<grd,blk>>>( d_sc,d_cc, nwidget, pitchpatch, d_w,d_ss,tol);
      if( i_pursuit==9)    kern_omp<10><<<grd,blk>>>( d_sc,d_cc, nwidget, pitchpatch, d_w,d_ss,tol);

      if( i_pursuit >6) {
	printf(" Error : aumenta i template per npursuit\n");
	exit(1);
      }
    }

    CUDA_SAFE_CALL(cudaMemcpy ( d_R,  d_y  ,  nwidget*sizepatch*sizeof(float)   ,cudaMemcpyDeviceToDevice  ));
    // AMMENDA
    for(ic=0; ic <= i_pursuit; ic++) {
      dim3 blk,grd;
      blk = dim3( 256 , 1 , 1 );
      grd = dim3( iDivUp( nwidget*sizepatch ,256) , 1, 1 );

      kern_subtract<<<grd,blk>>>(d_R, d_vects[ic],  d_w+ic*pitchpatch  , nwidget*sizepatch,  sizepatch  );

    }
  }

  {
    float alpha= -1.0f;
    cublasSaxpy ( sizepatch*nwidget ,  alpha, d_R, 1, d_y, 1);
  }

  CUDA_SAFE_CALL(cudaMemcpy  ( pimg , d_y ,  nwidget*sizepatch*sizeof(float)   ,cudaMemcpyDeviceToHost  ));
}



float  tv_denoising_patches_OMP(Gpu_Context * self,int dim0,int dim1,float  *img,float  *imgresult , float weight,
				float *comps, int n_comps, int dim_patches )  {

  //printf("dim_patches %d\n", dim_patches);

  int np0    = (dim0/dim_patches);
  int np1    = (dim1/dim_patches);
  //int marge0 = np0*dim_patches;
  //int marge1 = np1*dim_patches;
  float * pimg = (float *) malloc( dim0*dim1*sizeof(float));

  float * dists = (float *) malloc( dim0*dim1*sizeof(float));
  float * freqs = (float *) malloc( dim0*dim1*sizeof(float));

  float * tmp  = (float *) malloc( dim0*dim1*sizeof(float));
  float * tmp2  = (float *) malloc( (np0+1)*(np1+1)*sizeof(float));
  float * paverages  = (float *) malloc( (np0+1)*(np1+1)*sizeof(float));
  float *d_y, *d_R, *d_X,  *d_w, *d_dw, *d_ss,  *d_sc, *d_cc;
  float res=0.0f;
  float *comp_scalprods, *cs_scalprods, *cr_scalprods;
  int npursuit = (int) weight;
  float tol = weight-npursuit ;
  //printf(" QUI weight  %e \n", weight );

  int   *choosedcomps = (int*)  malloc( npursuit * (np0+1)*(np1+1)*sizeof(int));
  int i;

  int pitchpatch =  iDivUp( (np0+1)*(np1+1)  ,32)*32 ;
  cuCtxSetCurrent ( *((CUcontext *) self->gpuctx  ))  ;

  memcpy(imgresult, img, dim1*dim0*sizeof(float) );

  int sizepatch = dim_patches*dim_patches ;

  CUDA_SAFE_CALL( cudaMalloc(   &d_R,    dim0*dim1*sizeof(float)    ) ) ;
  CUDA_SAFE_CALL( cudaMalloc(   &d_y,    dim0*dim1*sizeof(float)    ) ) ;
  CUDA_SAFE_CALL( cudaMalloc(   &d_X,    n_comps* sizepatch *sizeof(float)    ) )   ;
  // CUDA_SAFE_CALL( cudaMalloc(    &d_tmp,  (np0+1)*(np1+1)*n_comps*sizeof(float)    ) )   ;
  CUDA_SAFE_CALL( cudaMalloc(   &d_w,    pitchpatch*npursuit*sizeof(float)    ) )   ;
  CUDA_SAFE_CALL( cudaMalloc(   &d_dw,   (np0+1)*(np1+1)*n_comps*sizeof(float)    ) )   ;

  CUDA_SAFE_CALL( cudaMalloc(   &d_ss,  pitchpatch*sizeof(float)    ) ) ;
  CUDA_SAFE_CALL( cudaMalloc(   &d_sc,  npursuit*pitchpatch*sizeof(float)    ) )    ;
  CUDA_SAFE_CALL( cudaMalloc(   &d_cc,  npursuit*npursuit*pitchpatch*sizeof(float)    ) )   ;

  comp_scalprods = (float*) malloc( n_comps* n_comps  *sizeof(float)  );
  cs_scalprods   = (float*) malloc( (np0+1)*(np1+1)  *n_comps* sizeof(float)  );
  cr_scalprods   = (float*) malloc( (np0+1)*(np1+1)  *n_comps* sizeof(float)  );

  float *d_vects[npursuit];
  for(i=0; i<npursuit; i++) {
    CUDA_SAFE_CALL( cudaMalloc( &(d_vects[i]) , (np0+1)*(np1+1)  * sizepatch*sizeof(float)  ) ) ;
    CUDA_SAFE_CALL( cudaMemset( d_vects[i],  0    , (np0+1)*(np1+1)  * sizepatch *sizeof(float)    ) )  ;
  }

  CUDA_SAFE_CALL(cudaMemcpy (   d_X , comps , n_comps* sizepatch *sizeof(float)   ,cudaMemcpyHostToDevice  ));
  {
    float *d_comp_scalprods;
    CUDA_SAFE_CALL( cudaMalloc(  &d_comp_scalprods,  n_comps*n_comps*sizeof(float)  ));

    float alpha= 1.0f;
    float beta = 0.0f;

    cublasSgemm('T','N',n_comps , n_comps ,  sizepatch ,
		alpha,
		d_X    , sizepatch ,
		d_X    , sizepatch  ,
		beta,
		d_comp_scalprods  , n_comps );

    CUDA_SAFE_CALL(cudaMemcpy(comp_scalprods , d_comp_scalprods , n_comps*n_comps*sizeof(float)   ,cudaMemcpyDeviceToHost  ));
    CUDA_SAFE_CALL(cudaFree( d_comp_scalprods ));
  }
  {
    memset(freqs, 0,  dim0*dim1*sizeof(float)  );
    for(i=0; i< dim0*dim1; i++) {
      dists[i]=1.0e10;
    }
    int shift0, shift1;
    for( shift0=0; shift0<dim_patches ; shift0+=self->STEPFORPATCHES ) {
      for( shift1=0; shift1<dim_patches ; shift1+=self->STEPFORPATCHES ) {
	int nw0    = ((dim0-shift0)/dim_patches);
	int nw1    = ((dim1-shift1)/dim_patches);

	// printf(" QUI tol -- %e %d %d\n", tol, self->STEPFORPATCHES,dim_patches );
	put_patches(pimg,img, dim1, shift0,shift1,nw0,nw1, dim_patches ,1 , paverages, dists, freqs);
	tv_denoising_patches_this_OMP( d_y, d_R, d_X,comps,   d_w , d_dw,  nw0*nw1  , n_comps, dim_patches,
				       pimg , comp_scalprods , cs_scalprods,
				       d_vects ,  tmp,  tmp2, choosedcomps, npursuit, d_ss, d_sc, d_cc, pitchpatch, cr_scalprods,
				       tol);

	put_patches(pimg,imgresult, dim1, shift0,shift1,nw0,nw1, dim_patches ,-1 , paverages, dists, freqs);
      }
    }
  }


  free(tmp);
  free(tmp2);

  for(i=0; i<npursuit; i++) {
    CUDA_SAFE_CALL( cudaFree( d_vects[i])  )    ;
  }

  free(dists);
  free(freqs);
  free(comp_scalprods);
  free(pimg);
  free(paverages);
  free(choosedcomps);
  free(cs_scalprods   );
  free(cr_scalprods   );


  CUDA_SAFE_CALL(cudaFree(d_R  ));
  CUDA_SAFE_CALL(cudaFree(d_y  ));
  CUDA_SAFE_CALL(cudaFree(d_X  ));
  // CUDA_SAFE_CALL(cudaFree(d_tmp));
  CUDA_SAFE_CALL(cudaFree(d_w  ));
  CUDA_SAFE_CALL(cudaFree(d_dw ));
  CUDA_SAFE_CALL(cudaFree(d_ss ));
  CUDA_SAFE_CALL(cudaFree(d_sc ));
  CUDA_SAFE_CALL(cudaFree(d_cc ));

  return res;
}


// =========================================================================================================

float  tv_denoising_fistagpu(Gpu_Context * self,int dim0,int dim1,float  *img,float  *result , float weight,
			     float eps , int n_iter_max,   int check_gap_frequency )  {

  cuCtxSetCurrent ( *((CUcontext *) self->gpuctx  ))  ;

  int nDums = 11;
  float *d_dums[nDums];
  float dgap;
  int i_img      = 0;
  int i_grad_tmp0 = 1;
  int i_grad_tmp1 = 2;
  int i_grad_aux0 = 3;
  int i_grad_aux1 = 4;
  int i_grad_im0  = 5;
  int i_grad_im1  = 6;
  int i_gap       = 7;
  int i_new       = 8;
  int i_normgrad       = 9 ;
  int i_error     = 10;

  size_t pitch_img  ;
  int Pdim;
  {
    for(int i=0; i<nDums; i++) {
      CUDA_SAFE_CALL( cudaMallocPitch(  &d_dums[i],  &pitch_img,   dim1*sizeof(float)   , dim0  ) ) ;
      CUDA_SAFE_CALL( cudaMemset     (   d_dums[i],  0  ,  pitch_img  * dim0  ) )   ;
      assert(     (  pitch_img %sizeof(float))==0);  // ceci devrait etre toujours vrai.
                                                     // Surtout il le doit : Sinon je ne peux pas utiliser les dsum et scal de cublas
      Pdim  =  pitch_img / sizeof(float)  * dim0;
    }
    CUDA_SAFE_CALL(cudaMemcpy2D ( d_dums[i_img], pitch_img, img,  dim1*sizeof(float), dim1*sizeof(float), dim0,  cudaMemcpyHostToDevice)) ;
  }

  float im_norm  ;
  // im_norm = cublasSdot (Pdim,   d_dums[i_img ]    , 1 , d_dums[i_img ]  , 1);
  im_norm =  cublasSnrm2(Pdim,   d_dums[i_img ]    , 1);
  im_norm*=im_norm;



  int Gdim0, Gdim1;
  {
    Gdim0 = iAlignUp(dim0, blksizeY);
    Gdim1 = iAlignUp(dim1, blksizeX);
  }

  int grdsizeY, grdsizeX ;
  {
    grdsizeY = Gdim0 / blksizeY ;
    grdsizeX = Gdim1 / blksizeX ;
  }
  dim3 dimGrid ( grdsizeX ,  grdsizeY  );
  dim3 dimBlock ( blksizeX , blksizeY    );

  cudaChannelFormatDesc ch_desc = cudaCreateChannelDesc<float>();
  Img_texture.filterMode = cudaFilterModePoint;

  Der0_texture.filterMode = cudaFilterModePoint;
  Der1_texture.filterMode = cudaFilterModePoint;

  float t = 1.0f;
  float t_new, t_factor;
  int   i = 0   ;
  while( i < n_iter_max) {

    // error = weight * div(grad_aux) - im
    //
    CUDA_SAFE_CALL( cudaBindTexture2D( NULL,Der0_texture , d_dums[i_grad_aux0], ch_desc, dim0, dim1,pitch_img )) ;
    CUDA_SAFE_CALL( cudaBindTexture2D( NULL,Der1_texture , d_dums[i_grad_aux1], ch_desc, dim0, dim1,pitch_img )) ;

    div_kernel  <<<dimGrid,dimBlock>>> (dim0, dim1,pitch_img , d_dums[i_error ] ,  weight );
    cublasSaxpy (Pdim, -1.0f ,  d_dums[i_img ]  , 1 ,d_dums[i_error ] , 1 );

    // grad_tmp = gradient(error)
    // grad_tmp *= 1./ (8 * weight)
    // grad_aux += grad_tmp

    CUDA_SAFE_CALL( cudaBindTexture2D( NULL,Img_texture , d_dums[i_error], ch_desc, dim0, dim1,pitch_img )) ;

    grad_kernel <<<dimGrid,dimBlock>>> (dim0, dim1,pitch_img,  d_dums[i_grad_tmp0 ],  d_dums[i_grad_tmp1 ] );

    cublasSaxpy (Pdim, 1.0f/ (8.0f * weight) ,   d_dums[i_grad_tmp0 ] , 1 ,d_dums[ i_grad_aux0] , 1 );
    cublasSaxpy (Pdim, 1.0f/ (8.0f * weight) ,   d_dums[i_grad_tmp1 ] , 1 ,d_dums[ i_grad_aux1] , 1 );



    //   grad_tmp = _projector_on_dual(grad_aux)
    projdual_kernel<<<dimGrid,dimBlock>>> (dim0, dim1,pitch_img,
					   d_dums[i_grad_tmp0 ],  d_dums[i_grad_tmp1 ],
					   d_dums[i_grad_aux0 ],  d_dums[i_grad_aux1 ] );


    // t_new = 1. / 2 * (1 + np.sqrt(1 + 4 * t**2))
    // t_factor = (t - 1) / t_new
    t_new = 1.0f / 2.0f * (1.0f + sqrt(1.0f + 4.0f * t*t ));
    t_factor = (t - 1.0f) / t_new  ;

    // grad_aux = (1 + t_factor) * grad_tmp - t_factor * grad_im
    // grad_im = grad_tmp
    // t = t_new

    linearcomb_kernel <<<dimGrid,dimBlock>>>( dim0,   dim1, pitch_img ,
					      d_dums[i_grad_aux0 ] ,
					      d_dums[i_grad_tmp0 ] ,(1 + t_factor) ,
					      d_dums[i_grad_im0  ] ,-t_factor ) ;
    linearcomb_kernel <<<dimGrid,dimBlock>>>( dim0,   dim1, pitch_img ,
					      d_dums[i_grad_aux1 ] ,
					      d_dums[i_grad_tmp1 ] ,(1 + t_factor) ,
					      d_dums[i_grad_im1  ] ,-t_factor ) ;

    // {
    //  printf(" PROBE %e \n",  probe( d_dums[ i_grad_aux0] , Pdim  ));
    //  printf(" %e %e  \n", t_new, t_factor  );
    //  if(i==1) return;
    // }

    CUDA_SAFE_CALL(cudaMemcpy  (  d_dums[i_grad_im0], d_dums[ i_grad_tmp0 ],  dim0*pitch_img   ,cudaMemcpyDeviceToDevice  ));
    CUDA_SAFE_CALL(cudaMemcpy  (  d_dums[i_grad_im1], d_dums[ i_grad_tmp1 ],  dim0*pitch_img   ,cudaMemcpyDeviceToDevice  ));

    t = t_new;

    //
    if ( (i % check_gap_frequency) == 0 ) {

      // gap = weight * div(grad_im)
      // new = im - gap
      CUDA_SAFE_CALL( cudaBindTexture2D( NULL,Der0_texture , d_dums[i_grad_im0], ch_desc, dim0, dim1,pitch_img )) ;
      CUDA_SAFE_CALL( cudaBindTexture2D( NULL,Der1_texture , d_dums[i_grad_im1], ch_desc, dim0, dim1,pitch_img )) ;
      div_kernel  <<<dimGrid,dimBlock>>> (dim0, dim1,pitch_img , d_dums[ i_gap ] ,  weight );
      linearcomb_kernel <<<dimGrid,dimBlock>>>( dim0,   dim1, pitch_img ,
						d_dums[i_new ] ,
						d_dums[i_img ] ,  1.0f ,
						d_dums[i_gap  ], -1.0f  ) ;



      // {
      //    printf(" PROBE %e \n",  probe( d_dums[i_new ] , Pdim  ));
      //    //return ;
      // }

      //  dgap = dual_gap(im, new, gap, weight)
      dgap =  dual_gap(dimGrid, dimBlock,
		       dim0, dim1,
		       im_norm,
		       d_dums[i_new ] ,
		       d_dums[i_gap ] ,
		       weight,
		       d_dums[i_normgrad ],
		       pitch_img,
		       ch_desc)  ;

      //
      if(dgap<eps) break;
    }
    i+=1;

  }
  if(i==n_iter_max) {
    printf("  tv_denoising counter reached allowed  maximum. Now exiting with dgap=%e\n", dgap);
  }


  CUDA_SAFE_CALL( cudaBindTexture2D( NULL,Img_texture ,d_dums[i_new ], ch_desc, dim0, dim1,pitch_img )) ;
  normgrad_kernel<<<dimGrid,dimBlock>>> (dim0, dim1,pitch_img, d_dums[i_normgrad ]  );

  float normgrad =  cublasSasum (  dim0* pitch_img/sizeof(float) ,d_dums[i_normgrad ], 1);


  // return new
  {
    CUDA_SAFE_CALL(cudaMemcpy2D  (result , dim1*sizeof(float) , d_dums[ i_new ], pitch_img , dim1*sizeof(float), dim0,  cudaMemcpyDeviceToHost)) ;
  }

  //
  {
    for(int i=0; i<nDums; i++) {
      CUDA_SAFE_CALL(cudaFree(d_dums[i]));
    }
  }
  return normgrad;
}



texture<float, 2, cudaReadModeElementType> texImage;

#define RADIUS   7
#define BRADIUS    5
#define AREA     ( (2 * BRADIUS + 1) * (2 * BRADIUS + 1) )
#define INV_AREA ( 1.0f / (float)AREA )

__global__ void NLM(
		    float *result,
		    int W,
		    int H,
		    float bruit,
		    float mix
		    )
{
  float bruit2=bruit*bruit;
  float Hf= bruit2*0.4f  ;
  const int ix = blockDim.x * blockIdx.x + threadIdx.x;
  const int iy = blockDim.y * blockIdx.y + threadIdx.y;
  const float x = (float)ix + 0.5f;
  const float y = (float)iy + 0.5f;
  int count=0;
  if (ix < W && iy < H)
    {
      float sumW = 0.0f ;
      float res = 0.0f ;
      float maxw=0.0f;
      for (float i = -RADIUS; i <= RADIUS; i++) {
	for (float j = -RADIUS; j <= RADIUS; j++)
	  {
	    if( i!=0 || j!=0  ) {
	      float distance = 0.0f;
	      float diff;
	      for (float n = -BRADIUS; n <= BRADIUS; n++) {
		for (float m = -BRADIUS; m <= BRADIUS; m++) {
		  diff = tex2D(texImage, x + j + m, y + i + n)-tex2D(texImage,     x + m,     y + n);
		  distance += diff*diff ;
		}
	      }
	      distance*= INV_AREA;
	      distance  =   min(max(distance-2*bruit2 , 0.0f  ),100.0f*Hf)  ;
	      float weight     = __expf(- distance/Hf  );
	      if( weight >1.0e-10) {
		count++;
		float value = tex2D(texImage, x + j, y + i);
		res      += value * weight ;
		sumW  += weight ;
		if(weight>maxw) maxw=weight;
	      }
	    }
	  }
      }
      float orig = tex2D(texImage, x, y);
      if(count) {
	res   = res +maxw*orig;
	sumW += maxw;
	res /= sumW;
	result[W * iy + ix] =  res*mix+(1.0f-mix)*orig ;
      } else {
	result[W * iy + ix] = orig;
      }
    }
}

void nonlocalmeans(    Gpu_Context * self,
		       float *result,
		       float *image,
		       int H,
		       int W,
		       float bruit,
		       float mix
		       )
{

  cuCtxSetCurrent ( *((CUcontext *) self->gpuctx  ))  ;

  cudaArray *a_image;
  cudaMallocArray  (&a_image, &floatTex, W, H);
  cudaMemcpyToArray( a_image, 0, 0, image, H*W* sizeof(float),cudaMemcpyHostToDevice );
  CUDA_SAFE_CALL( cudaBindTextureToArray(texImage,(cudaArray*)a_image ) );


  float *d_result;
  cudaMalloc((void **)&d_result, H*W*sizeof(float));

  dim3 dimGrid (iDivUp(W,blksizeX ), iDivUp(H,blksizeY ));
  dim3 dimBlock (blksizeX, blksizeY);


  NLM<<<dimGrid,dimBlock >>>(d_result, W, H, bruit, mix);

  cudaMemcpy( result, d_result , H*W*sizeof(float), cudaMemcpyDeviceToHost);
 
  CUDA_SAFE_CALL(cudaFree(d_result) );
  CUDA_SAFE_CALL(cudaFreeArray(a_image) );
  CUDA_SAFE_CALL( cudaUnbindTexture     (   texImage     ));
}


#define MATVECT_WKSIZE 128
__global__ static void matvectmult_coo( int N,
					int Npt,
					const int   * tos ,
					const int   * froms , 
					const float * coeffs,
					const float * input,
					float * output,
					float beta
					)
  
{
  volatile __shared__  float terms[GPU_LT_MAX_NDIFF4thread*MATVECT_WKSIZE];
  __shared__  int iterms[GPU_LT_MAX_NDIFF4thread*MATVECT_WKSIZE];
  volatile __shared__  int islastterm[MATVECT_WKSIZE];

  int gid = threadIdx.x + blockIdx.x*blockDim.x;
  const int tid = threadIdx.x ;
  while ( (gid-tid)*Npt < N )
    {
      int inizio = gid*Npt;
      int fine   = (gid+1)*Npt;
      if(fine>N) fine=N;
      
      int nterms=0;
      int last=-1;
      islastterm[tid]=0;
      iterms[ tid ]=-1;
      
      int i,j;
      float c;
      
      
      for(int k=inizio; k< fine; k++) {
	i = tos  [k];
	j = froms[k];
	c = coeffs[k];
	if(  i != last ) {
	  nterms++;
	  terms [ MATVECT_WKSIZE*(nterms-1)+tid]=  0.0f;
	  iterms[ MATVECT_WKSIZE*(nterms-1)+tid]=  i   ;
	  last=i;
	}
	terms [ MATVECT_WKSIZE*(nterms-1) + tid] += c* input [ j ] ;
      }
      if( tid == (MATVECT_WKSIZE-1)  ) islastterm[tid] = nterms; 
      __syncthreads () ;
      
      
      int d=2;
      while(d<= MATVECT_WKSIZE) {
	if(tid%d==0 && fine>inizio) {
	  int mylast = iterms[ MATVECT_WKSIZE*(nterms-1)+tid];
	  if(mylast ==   iterms[ tid +d/2]   ) {
	    terms[ MATVECT_WKSIZE*(nterms-1)+tid] +=  terms[ tid +d/2] ;
	    terms[ tid+d/2] =  0.0f;
	    if( islastterm[tid+d/2]==1 ) {
	      islastterm[tid]==nterms;
	    }
	  }
	  
	}
	__syncthreads ();
	if(  (tid+d/2)%d==0  &&  (tid+d/2)<MATVECT_WKSIZE && fine>inizio) {
	  int mylast = iterms[ MATVECT_WKSIZE*(nterms-1)+tid];
	  if(mylast ==   iterms[ tid +d/2]   ) {
	    terms[ tid +d/2]  += terms[ MATVECT_WKSIZE*(nterms-1)+tid] ;
	    terms  [ MATVECT_WKSIZE*(nterms-1)+tid  ] =  0.0f;
	  }
	}
	d*=2;
	__syncthreads ();
      }
      for(int n=0; n<nterms; n++) {
	float add ; 
	add = terms[ MATVECT_WKSIZE*n+tid];
	
	int iplace = iterms[MATVECT_WKSIZE*n+tid ];
	if( add!=0.0f) {
	  if(   ( (tid==0 && n==0) || ( islastterm[tid] && n==(nterms-1)))) {
	    atomicAdd(  output+iplace, add*beta  );
	  } else {
	    output[iplace] = output[iplace]  + add*beta ; 
	  }
	}
      }
      gid += blockDim.x * gridDim.x ;
    }
}
__global__ static void   lt_crop_off_tails ( float *d_diff , 
					     int nprojs,
					     int NBins,
					     int ltmxstp  
					     ) {
  int gid = threadIdx.x + blockIdx.x*blockDim.x;
  while ( gid < nprojs*NBins   ) {
    int ix = gid%NBins;
    if(ix<3*ltmxstp || ix >= NBins-3*ltmxstp) {
      d_diff[gid]=0.0f;
    }
    gid += blockDim.x * gridDim.x ;
  }
}

__global__ static void    lt_gauss_mult_kern( float *Cmultisino , 
					      int SLD,
					      float sigma,
					      int NBins  
					      ) {
  int nprojs =  SLD/ NBins ;
  int Nr2c  = (NBins/2+1);
  int gid = threadIdx.x + blockIdx.x*blockDim.x;
  while ( gid < nprojs*(NBins/2+1)   ) {
    int ix = gid%Nr2c;
    float fact = ix*2*M_PI/NBins   * sigma;
    fact = exp( - fact*fact/2.0f)/NBins *  M_PI/(2*nprojs)  ; 
    Cmultisino[2*gid  ] *=  fact ; 
    Cmultisino[2*gid+1] *=  fact ; 
    gid += blockDim.x * gridDim.x ;
  }
}



double gpu_lt_calculate_grad_sino( Gpu_Context * self, int Ng,float *d_grad,float * d_gaussian_coeffs,
				  int   Ns ,  float *d_data, int N,
				  int *d_csc_II,  int *d_csc_JJ  , float *d_csc_C , int csc_nitems_atonce,
				  int *d_csr_II,   int *d_csr_JJ , float *d_csr_C , int csr_nitems_atonce,
				  float **d_auxs,
				  float *d_multisino,int SLD,int  Nsigmas, float *  sigmas,
				  LT_infos* lt, LT_infos* lt2s)  {

  double err=0.0;
  float *d_diff      = d_auxs[0];
  // float *d_convolved = d_auxs[1];
  float *d_Cmultisino     = d_auxs[2];

  int NBins  =  Ns/Nsigmas/lt->nprojs           ; 
  // int nbins  =  NBins - 6*  lt->lt_max_step    ;

  CUDA_SAFE_CALL( cudaMemset(d_diff,0, (NBins*lt->nprojs+lt2s->dim2s_s)*sizeof(float)); );

  if (d_data ==NULL) {
  } else {
    assert(NBins*lt->nprojs== SLD) ;
    CUDA_SAFE_CALL( cudaMemcpy(d_diff,d_data ,(SLD+lt2s->dim2s_s)*sizeof(float), cudaMemcpyDeviceToDevice); );
  }

  CUDA_SAFE_CALL(cudaMemset(d_multisino, 0, sizeof(float)* (Nsigmas* SLD +lt2s->dim2s_s   ) ); );
  {
    dim3 blk = dim3(MATVECT_WKSIZE  , 1 , 1 );
    dim3 grd = dim3( 1024 , 1 , 1 );
    matvectmult_coo<<<grd,blk>>>( N, csr_nitems_atonce,
				  d_csr_II ,
				  d_csr_JJ , 
				  d_csr_C ,
				  d_gaussian_coeffs,
				  d_multisino ,
				  1.0f
				  );
  }
  CUDA_SAFE_FFT(cufftExecR2C((cufftHandle ) (self->lt_planr2c),
    (cufftReal *)d_multisino,
    (cufftComplex *)d_Cmultisino));
  {
    dim3 blk = dim3( 512  , 1 , 1 );
    dim3 grd = dim3( 1024 , 1 , 1 );
    for(int is=0; is<Nsigmas; is++) {
      lt_gauss_mult_kern<<<grd,blk>>>(  d_Cmultisino+2*is*(lt->nprojs*(NBins/2+1)) , 
					SLD,
					sigmas[is],
					NBins  
					);
    }
  }
  CUDA_SAFE_FFT(cufftExecC2R((cufftHandle ) (self->lt_planc2r),
			     (cufftComplex *)d_Cmultisino,
			     (cufftReal *)d_multisino));
  
  for(int is=0; is<Nsigmas; is++) {
    float alpha= -1.0f;
    cublasSaxpy ( SLD ,  alpha,d_multisino+is*SLD , 1,d_diff , 1);
  }
  {
    dim3 blk = dim3( 512  , 1 , 1 );
    dim3 grd = dim3( 1024 , 1 , 1 );
    lt_crop_off_tails<<<grd,blk>>>(d_diff,
				   lt->nprojs,
				   NBins,
				   lt->lt_max_step
				   );
  }
  {
    float alpha= -1.0f;
    cublasSaxpy ( lt2s->dim2s_s ,  alpha,d_multisino+Nsigmas*SLD , 1, d_diff + SLD , 1);
  }
  

  err = cublasSnrm2( (NBins*lt->nprojs+lt2s->dim2s_s) ,  d_diff   , 1);
  err = err*err/2.0 ;


  CUDA_SAFE_CALL(cudaMemset(d_multisino, 0, sizeof(float)* (Nsigmas* SLD +lt2s->dim2s_s   ) ); );
  
  for(int is=0; is<Nsigmas; is++) {
    float alpha= -1.0f;
    cublasSaxpy ( SLD ,  alpha,d_diff , 1,d_multisino+is*SLD , 1);
  }
  {
    float alpha= -1.0f;
    cublasSaxpy ( lt2s->dim2s_s ,  alpha,d_diff+SLD , 1,d_multisino+Nsigmas*SLD , 1);
  }

  CUDA_SAFE_FFT(cufftExecR2C((cufftHandle ) (self->lt_planr2c),
    (cufftReal *)d_multisino,
    (cufftComplex *)d_Cmultisino));
  {
    dim3 blk = dim3( 512  , 1 , 1 );
    dim3 grd = dim3( 1024 , 1 , 1 );
    for(int is=0; is<Nsigmas; is++) {
      lt_gauss_mult_kern<<<grd,blk>>>(  d_Cmultisino+2*is*(lt->nprojs*(NBins/2+1)) , 
					SLD,
					sigmas[is],
					NBins  
					);
    }
  }
  
  CUDA_SAFE_FFT(cufftExecC2R((cufftHandle ) (self->lt_planc2r),
			     (cufftComplex *)d_Cmultisino,
			     (cufftReal *)d_multisino));
  
  CUDA_SAFE_CALL( cudaMemset(d_grad,0, Ng*sizeof(float)); );
  
  
  {
    dim3 blk = dim3(MATVECT_WKSIZE  , 1 , 1 );
    dim3 grd = dim3( 1024 , 1 , 1 );
    matvectmult_coo<<<grd,blk>>>( N, csc_nitems_atonce,
				  d_csc_JJ ,
				  d_csc_II , 
				  d_csc_C ,
				  d_multisino ,
				  d_grad,
				  1.0f
				  );
  }
  
  
  return err;
}
  


float gpu_lt_fit_sino(Gpu_Context * self, int Ng, float *gaussian_coeffs,   int   Ns , float * data, int N,
		      int *csc_II ,  int * csc_JJ ,float * csc_C , int  csc_nitems_atonce,
		      int *csr_II ,  int * csr_JJ ,float * csr_C , int  csr_nitems_atonce,
		      int SLD,  int Nsigmas,   float *sigmas,  LT_infos* lt, LT_infos *lt2s
		      ) {
  
  printf("CUCU da gpu_lt_fit_sino  \n");
  cuCtxSetCurrent ( *((CUcontext *) self->gpuctx  ))  ;

  void * tocancel[100];
  int Ntocancel = 0 ; 


    // Profiling
    cudaEvent_t tstart, tstop;
    cudaEventCreate(&tstart); cudaEventCreate(&tstop);
    float elapsedTime;
    cudaEventRecord(tstart, 0);
    // ---



  
#define SIH2D( csc_II, T , N)    T * d_ ## csc_II;		       \
  CUDA_SAFE_CALL( cudaMalloc( &d_ ## csc_II ,       N * sizeof(T)  ));	\
  CUDA_SAFE_CALL( cudaMemcpy ( d_ ## csc_II , csc_II ,  N*sizeof(T) , cudaMemcpyHostToDevice )) ; \
  tocancel[Ntocancel] = d_ ## csc_II  ;  \
  Ntocancel++
  
  
  SIH2D( csc_II , int ,N );
  SIH2D( csc_JJ , int ,N );
  
  SIH2D( csr_II , int ,N );
  SIH2D( csr_JJ , int ,N );
  
  SIH2D( csc_C , float ,N );
  SIH2D( csr_C , float ,N );

  int NBins  =  Ns/Nsigmas/lt->nprojs           ; 
  int nbins  =  NBins - 6*  lt->lt_max_step    ;
  
  float *d_auxs[9];
  int Adim[] = { Ng,Ng, Ng,Ng, Nsigmas* SLD + lt2s->dim2s_s ,SLD + lt2s->dim2s_s,  SLD+lt2s->dim2s_s,SLD,
		 Nsigmas* 2*( (NBins/2+1)*lt->nprojs) };
  for(int  i=0; i<9; i++)  {
    CUDA_SAFE_CALL(cudaMalloc(&(d_auxs[i]),  Adim[i]* sizeof(float)  ));
    CUDA_SAFE_CALL(cudaMemset(d_auxs[i], 0,  Adim[i]* sizeof(float)  ));
    tocancel[Ntocancel] = d_auxs[i]  ;    Ntocancel++;
  }

  
  float *d_grad=d_auxs[0];
  float *d_grad_old=d_auxs[1];
  float *d_p=d_auxs[2];
  float *d_gaussian_coeffs =  d_auxs[3];
  float *d_multisino = d_auxs[4];
  float *d_data   =    d_auxs[5];  

  CUDA_SAFE_CALL(cudaMemcpy2D(
			      d_data + 3*  lt->lt_max_step ,    (SLD/lt->nprojs) *sizeof(float),
			      data ,
			      nbins*sizeof(float),
			      nbins*sizeof(float),
			      lt->nprojs,
			      cudaMemcpyHostToDevice
			      ););

  CUDA_SAFE_CALL( cudaMemcpy ( d_data + SLD , lt2s->slice_comp ,   lt2s->dim2s_s*sizeof(float) , cudaMemcpyHostToDevice )) ; \

  CUDA_SAFE_CALL(cudaMemset( d_gaussian_coeffs, 0 , sizeof(float) *  Ng ) ); 
  
  double err;
  printf(" CHIAMO CALC PER LA PRIMA VOLTA \n");
  
  err=gpu_lt_calculate_grad_sino( self, Ng,d_grad, d_gaussian_coeffs,     Ns ,  d_data, N,
				  d_csc_II,  d_csc_JJ ,d_csc_C , csc_nitems_atonce,
				  d_csr_II,  d_csr_JJ ,d_csr_C , csr_nitems_atonce,
				  d_auxs+6,
				  d_multisino, SLD,  Nsigmas,   sigmas,  lt, lt2s) ;
  
  CUDA_SAFE_CALL( cudaMemcpy ( d_p,d_grad  ,   Ng*sizeof(float) , cudaMemcpyDeviceToDevice )) ; 
  double rrold,rr;
  if(USECUDADOT) {
    rrold = cudaDot( Ng,  d_grad  , d_grad  ) ;
  } else {
    rrold =  cublasSnrm2( Ng ,  d_grad   , 1);
    rrold *= rrold;
  }

  rr = rrold; 
  for ( int iter=0; iter<300; iter++) {
    //printf("ITER %d \n", iter);
    CUDA_SAFE_CALL( cudaMemcpy ( d_grad_old,d_grad  ,   Ng*sizeof(float) , cudaMemcpyDeviceToDevice )) ; 
    gpu_lt_calculate_grad_sino( self, Ng,d_grad, d_p,     Ns ,  NULL , N,
				d_csc_II,  d_csc_JJ ,d_csc_C , csc_nitems_atonce,
				d_csr_II,  d_csr_JJ ,d_csr_C , csr_nitems_atonce,
				d_auxs+6,
				d_multisino, SLD,  Nsigmas,   sigmas,  lt, lt2s) ;    

    double pap;
    if (USECUDADOT) {
      pap =  cudaDot( Ng, d_p, d_grad   ) ;
    } else    {
      cublasSaxpy ( Ng ,  1.0 , d_p  , 1  , d_grad , 1  );
      double ap = cublasSnrm2( Ng ,  d_grad   , 1) ;
      
      ap=ap*ap;
      
      cublasSaxpy ( Ng ,  -2.0 , d_p  , 1  , d_grad , 1  );
      double am = cublasSnrm2( Ng ,  d_grad   , 1) ;
      
      am=am*am;
      
      pap = ( ap-am  )/4.0 ; 
      
      cublasSaxpy ( Ng ,  1.0 , d_p  , 1  , d_grad , 1  );

    }


    
    CUDACHECK;
    cublasSaxpy ( Ng ,  -rr/pap , d_p  , 1  ,  d_gaussian_coeffs , 1  );
    CUDACHECK;
    err=gpu_lt_calculate_grad_sino( self, Ng,d_grad, d_gaussian_coeffs,     Ns ,  d_data, N,
				    d_csc_II,  d_csc_JJ ,d_csc_C , csc_nitems_atonce,
				    d_csr_II,  d_csr_JJ ,d_csr_C , csr_nitems_atonce,
				    d_auxs+6,
				    d_multisino, SLD,  Nsigmas,   sigmas,  lt, lt2s) ;
    rrold = rr ;
    if(USECUDADOT) {
      rr    = cudaDot( Ng,  d_grad  , d_grad  ) ;
    } else {
      rr =  cublasSnrm2( Ng ,  d_grad   , 1);
      rr *= rr;
    }

    
    float beta = max(rr/rrold,0)  ;
    // beta=0;
    cublasSaxpy ( Ng ,  beta , d_p  , 1  ,  d_grad , 1  );
    CUDA_SAFE_CALL( cudaMemcpy ( d_p , d_grad,   Ng*sizeof(float) , cudaMemcpyDeviceToDevice )) ; \
    

    
    if ((iter % 10) == 0) printf( "[%d] LT_gpu errore est %e  mod_grad est  %e  beta %e\n", iter, err,rr, beta) ;
  }
  CUDA_SAFE_CALL( cudaMemcpy ( gaussian_coeffs , d_gaussian_coeffs,   Ng*sizeof(float) , cudaMemcpyDeviceToHost )) ; \
  
  for(int i=0; i<Ntocancel;i++) {
    cudaFree(tocancel[i]);
  }
  // Profiling
  cudaEventRecord(tstop, 0); cudaEventSynchronize(tstop); cudaEventElapsedTime(&elapsedTime, tstart, tstop);
  printf("GPU LT took %lf s\n", elapsedTime/1e3);
  printf("N2 = %d, Ng = %d\n", SLD/lt->nprojs,Ng); // P.numpjs_span * (num_bins +6*P.LT_MAX_STEP)
  // ---

  return err ; 
}










//  You can do the following:

//     texture<float2, 1, cudaReadModeElementType> data_d_texture_filtering;



//     __global__ void linear_interpolation_kernel_function_GPU_texture_filtering(float2* __restrict__ result_d, const float* __restrict__ x_out_d, const int M, const int N)
//     {
//        int j = threadIdx.x + blockDim.x * blockIdx.x; 
//        if(j<N) result_d[j] = tex1D(data_d_texture_filtering,float(x_out_d[j]+M/2+0.5));
//     }



// and

//     void linear_interpolation_function_GPU_texture_filtering(float2* result_d, float2* data, float* x_in_d, float* x_out_d, int M, int N){
       
//     	cudaArray* data_d = NULL; cudaMallocArray (&data_d, &data_d_texture_filtering.channelDesc, M, 1);
//     	cudaMemcpyToArray(data_d, 0, 0, data, sizeof(float2)*M, cudaMemcpyHostToDevice);
//     	cudaBindTextureToArray(data_d_texture_filtering, data_d);
//     	data_d_texture_filtering.normalized = false;
//     	data_d_texture_filtering.filterMode = cudaFilterModeLinear;
       
//     	dim3 dimBlock(BLOCK_SIZE,1); dim3 dimGrid(N/BLOCK_SIZE + (N%BLOCK_SIZE == 0 ? 0:1),1);
//        	linear_interpolation_kernel_function_GPU_texture_filtering<<<dimGrid,dimBlock>>>(result_d, x_out_d, M, N);
//     }

// wwww.orangeowlsolutions.com


