Commit 2e1c9711 authored by BLANCHARD Pierre's avatar BLANCHARD Pierre

Redesigned direct DFT and FFT classes (provided a separate complex valued FFT...

Redesigned direct DFT and FFT classes (provided a separate complex valued FFT and templatized direct DFT with physical value type); Provided test for C-interfaced Lapack functions such as Cholesky Decomposition (TODO add SVD and QRD).
parent fb5cedd0
...@@ -66,8 +66,10 @@ static void Compute(const MatrixKernelClass *const MatrixKernel, const FReal Cel ...@@ -66,8 +66,10 @@ static void Compute(const MatrixKernelClass *const MatrixKernel, const FReal Cel
TensorType::setNodeIdsPairs(node_ids_pairs); TensorType::setNodeIdsPairs(node_ids_pairs);
// init Discrete Fourier Transformator // init Discrete Fourier Transformator
const int dimfft = 1; // unidim FFT since fully circulant embedding
const int steps[dimfft] = {rc};
// FDft Dft(rc); // FDft Dft(rc);
FFft<1> Dft(rc); FFft<dimfft> Dft(steps);
// get first column of K via permutation // get first column of K via permutation
unsigned int perm[rc]; unsigned int perm[rc];
...@@ -177,15 +179,17 @@ class FUnifM2LHandler<ORDER,HOMOGENEOUS> : FNoCopyable ...@@ -177,15 +179,17 @@ class FUnifM2LHandler<ORDER,HOMOGENEOUS> : FNoCopyable
FComplexe *FC; FComplexe *FC;
// for real valued kernel only n/2+1 complex values are stored
// after performing the DFT (the rest is deduced by conjugation)
unsigned int opt_rc;
typedef FUnifTensor<ORDER> TensorType; typedef FUnifTensor<ORDER> TensorType;
unsigned int node_diff[nnodes*nnodes]; unsigned int node_diff[nnodes*nnodes];
// FDft Dft; // Direct Discrete Fourier Transformator /// DFT specific
FFft<1> Dft; // Fast Discrete Fourier Transformator static const int dimfft = 1; // unidim FFT since fully circulant embedding
// FDft Dft; // Direct Discrete Fourier Transformator
typedef FFft<dimfft> DftClass; // Fast Discrete Fourier Transformator
FSmartPointer<DftClass,FSmartPointerMemory> Dft;
const unsigned int opt_rc; // specific to real valued kernel
static const std::string getFileName() static const std::string getFileName()
{ {
...@@ -201,9 +205,12 @@ public: ...@@ -201,9 +205,12 @@ public:
template <typename MatrixKernelClass> template <typename MatrixKernelClass>
FUnifM2LHandler(const MatrixKernelClass *const MatrixKernel, const unsigned int, const FReal) FUnifM2LHandler(const MatrixKernelClass *const MatrixKernel, const unsigned int, const FReal)
: FC(NULL), : FC(NULL),
opt_rc(rc/2+1), opt_rc(rc/2+1)
Dft(rc) // initialize Discrete Fourier Transformator
{ {
// init DFT
const int steps[dimfft] = {rc};
Dft = new DftClass(steps);
// initialize root node ids // initialize root node ids
TensorType::setNodeIdsDiff(node_diff); TensorType::setNodeIdsDiff(node_diff);
...@@ -253,7 +260,7 @@ public: ...@@ -253,7 +260,7 @@ public:
FReal Px[rc]; FReal Px[rc];
FBlas::setzero(rc,Px); FBlas::setzero(rc,Px);
// Apply forward Discrete Fourier Transform // Apply forward Discrete Fourier Transform
Dft.applyIDFT(FX,Px); Dft->applyIDFT(FX,Px);
// Unapply Zero Padding // Unapply Zero Padding
for (unsigned int j=0; j<nnodes; ++j) for (unsigned int j=0; j<nnodes; ++j)
...@@ -319,7 +326,7 @@ public: ...@@ -319,7 +326,7 @@ public:
Py[node_diff[i*nnodes]]=y[i]; Py[node_diff[i*nnodes]]=y[i];
// Apply forward Discrete Fourier Transform // Apply forward Discrete Fourier Transform
Dft.applyDFT(Py,FY); Dft->applyDFT(Py,FY);
} }
...@@ -342,15 +349,17 @@ class FUnifM2LHandler<ORDER,NON_HOMOGENEOUS> : FNoCopyable ...@@ -342,15 +349,17 @@ class FUnifM2LHandler<ORDER,NON_HOMOGENEOUS> : FNoCopyable
const unsigned int TreeHeight; const unsigned int TreeHeight;
const FReal RootCellWidth; const FReal RootCellWidth;
// for real valued kernel only n/2+1 complex values are stored
// after performing the DFT (the rest is deduced by conjugation)
unsigned int opt_rc;
typedef FUnifTensor<ORDER> TensorType; typedef FUnifTensor<ORDER> TensorType;
unsigned int node_diff[nnodes*nnodes]; unsigned int node_diff[nnodes*nnodes];
// FDft Dft; // Direct Discrete Fourier Transformator // DFT specific
FFft<1> Dft; // Fast Discrete Fourier Transformator static const int dimfft = 1; // unidim FFT since fully circulant embedding
// FDft Dft; // Direct Discrete Fourier Transformator
typedef FFft<dimfft> DftClass; // Fast Discrete Fourier Transformator
FSmartPointer<DftClass,FSmartPointerMemory> Dft;
const unsigned int opt_rc; // specific to real valued kernel
static const std::string getFileName() static const std::string getFileName()
{ {
...@@ -367,9 +376,12 @@ public: ...@@ -367,9 +376,12 @@ public:
FUnifM2LHandler(const MatrixKernelClass *const MatrixKernel, const unsigned int inTreeHeight, const FReal inRootCellWidth) FUnifM2LHandler(const MatrixKernelClass *const MatrixKernel, const unsigned int inTreeHeight, const FReal inRootCellWidth)
: TreeHeight(inTreeHeight), : TreeHeight(inTreeHeight),
RootCellWidth(inRootCellWidth), RootCellWidth(inRootCellWidth),
opt_rc(rc/2+1), opt_rc(rc/2+1)
Dft(rc) // initialize Discrete Fourier Transformator
{ {
// init DFT
const int steps[dimfft] = {rc};
Dft = new DftClass(steps);
// initialize root node ids // initialize root node ids
TensorType::setNodeIdsDiff(node_diff); TensorType::setNodeIdsDiff(node_diff);
...@@ -431,7 +443,7 @@ public: ...@@ -431,7 +443,7 @@ public:
FReal Px[rc]; FReal Px[rc];
FBlas::setzero(rc,Px); FBlas::setzero(rc,Px);
// Apply forward Discrete Fourier Transform // Apply forward Discrete Fourier Transform
Dft.applyIDFT(FX,Px); Dft->applyIDFT(FX,Px);
// Unapply Zero Padding // Unapply Zero Padding
for (unsigned int j=0; j<nnodes; ++j) for (unsigned int j=0; j<nnodes; ++j)
...@@ -496,7 +508,7 @@ public: ...@@ -496,7 +508,7 @@ public:
Py[node_diff[i*nnodes]]=y[i]; Py[node_diff[i*nnodes]]=y[i];
// Apply forward Discrete Fourier Transform // Apply forward Discrete Fourier Transform
Dft.applyDFT(Py,FY); Dft->applyDFT(Py,FY);
} }
......
...@@ -60,8 +60,10 @@ static void precompute(const MatrixKernelClass *const MatrixKernel, const FReal ...@@ -60,8 +60,10 @@ static void precompute(const MatrixKernelClass *const MatrixKernel, const FReal
_FC = new FComplexe [rc]; // TODO: do it in the non-sym version!!! _FC = new FComplexe [rc]; // TODO: do it in the non-sym version!!!
// init Discrete Fourier Transformator // init Discrete Fourier Transformator
const int dimfft = 1; // unidim FFT since fully circulant embedding
const int steps[dimfft] = {rc};
// FDft Dft(rc); // FDft Dft(rc);
FFft Dft(rc); FFft<dimfft> Dft(steps);
// reduce storage if real valued kernel // reduce storage if real valued kernel
const unsigned int opt_rc = rc/2+1; const unsigned int opt_rc = rc/2+1;
......
...@@ -48,9 +48,9 @@ class FTreeCoordinate; ...@@ -48,9 +48,9 @@ class FTreeCoordinate;
* In fact, in the ChebyshevSym variant the matrix kernel needs to be * In fact, in the ChebyshevSym variant the matrix kernel needs to be
* evaluated compo-by-compo since we currently use a scalar ACA. * evaluated compo-by-compo since we currently use a scalar ACA.
* *
* 3) We currently use multiple 1D FFT instead of multidim FFT. * 3) We currently use multiple 1D FFT instead of multidim FFT since embedding
* TODO switch to multidim if relevant in considered range of size * is circulant. Multidim FFT could be used if embedding were block circulant.
* (see testFFTW and testFFTWMultidim). * TODO investigate possibility of block circulant embedding
* *
* @tparam CellClass Type of cell * @tparam CellClass Type of cell
* @tparam ContainerClass Type of container to store particles * @tparam ContainerClass Type of container to store particles
......
...@@ -75,8 +75,10 @@ static void Compute(const MatrixKernelClass *const MatrixKernel, ...@@ -75,8 +75,10 @@ static void Compute(const MatrixKernelClass *const MatrixKernel,
TensorType::setNodeIdsPairs(node_ids_pairs); TensorType::setNodeIdsPairs(node_ids_pairs);
// init Discrete Fourier Transformator // init Discrete Fourier Transformator
const int dimfft = 1; // unidim FFT since fully circulant embedding
const int steps[dimfft] = {rc};
// FDft Dft(rc); // FDft Dft(rc);
FFft<1> Dft(rc); FFft<dimfft> Dft(steps);
// get first column of K via permutation // get first column of K via permutation
unsigned int perm[rc]; unsigned int perm[rc];
...@@ -198,15 +200,17 @@ class FUnifTensorialM2LHandler<ORDER,MatrixKernelClass,HOMOGENEOUS> : FNoCopyabl ...@@ -198,15 +200,17 @@ class FUnifTensorialM2LHandler<ORDER,MatrixKernelClass,HOMOGENEOUS> : FNoCopyabl
// Tensorial MatrixKernel specific // Tensorial MatrixKernel specific
FComplexe** FC; FComplexe** FC;
// for real valued kernel only n/2+1 complex values are stored
// after performing the DFT (the rest is deduced by conjugation)
unsigned int opt_rc;
typedef FUnifTensor<ORDER> TensorType; typedef FUnifTensor<ORDER> TensorType;
unsigned int node_diff[nnodes*nnodes]; unsigned int node_diff[nnodes*nnodes];
// FDft Dft; // Direct Discrete Fourier Transformator // DFT specific
FFft<1> Dft; // Fast Discrete Fourier Transformator static const int dimfft = 1; // unidim FFT since fully circulant embedding
// FDft Dft; // Direct Discrete Fourier Transformator
typedef FFft<dimfft> DftClass; // Fast Discrete Fourier Transformator
FSmartPointer<DftClass,FSmartPointerMemory> Dft;
const unsigned int opt_rc; // specific to real valued kernel
static const std::string getFileName() static const std::string getFileName()
{ {
...@@ -220,9 +224,12 @@ class FUnifTensorialM2LHandler<ORDER,MatrixKernelClass,HOMOGENEOUS> : FNoCopyabl ...@@ -220,9 +224,12 @@ class FUnifTensorialM2LHandler<ORDER,MatrixKernelClass,HOMOGENEOUS> : FNoCopyabl
public: public:
FUnifTensorialM2LHandler(const MatrixKernelClass *const MatrixKernel, const unsigned int, const FReal) FUnifTensorialM2LHandler(const MatrixKernelClass *const MatrixKernel, const unsigned int, const FReal)
: opt_rc(rc/2+1), : opt_rc(rc/2+1)
Dft(rc) // initialize Discrete Fourier Transformator
{ {
// init DFT
const int steps[dimfft] = {rc};
Dft = new DftClass(steps);
// allocate FC // allocate FC
FC = new FComplexe*[dim]; FC = new FComplexe*[dim];
for (unsigned int d=0; d<dim; ++d) for (unsigned int d=0; d<dim; ++d)
...@@ -277,7 +284,7 @@ public: ...@@ -277,7 +284,7 @@ public:
FReal Px[rc]; FReal Px[rc];
FBlas::setzero(rc,Px); FBlas::setzero(rc,Px);
// Apply forward Discrete Fourier Transform // Apply forward Discrete Fourier Transform
Dft.applyIDFT(FX,Px); Dft->applyIDFT(FX,Px);
// Unapply Zero Padding // Unapply Zero Padding
for (unsigned int j=0; j<nnodes; ++j) for (unsigned int j=0; j<nnodes; ++j)
...@@ -344,7 +351,7 @@ public: ...@@ -344,7 +351,7 @@ public:
Py[node_diff[i*nnodes]]=y[i]; Py[node_diff[i*nnodes]]=y[i];
// Apply forward Discrete Fourier Transform // Apply forward Discrete Fourier Transform
Dft.applyDFT(Py,FY); Dft->applyDFT(Py,FY);
} }
...@@ -369,15 +376,17 @@ class FUnifTensorialM2LHandler<ORDER,MatrixKernelClass,NON_HOMOGENEOUS> : FNoCop ...@@ -369,15 +376,17 @@ class FUnifTensorialM2LHandler<ORDER,MatrixKernelClass,NON_HOMOGENEOUS> : FNoCop
const unsigned int TreeHeight; const unsigned int TreeHeight;
const FReal RootCellWidth; const FReal RootCellWidth;
// for real valued kernel only n/2+1 complex values are stored
// after performing the DFT (the rest is deduced by conjugation)
unsigned int opt_rc;
typedef FUnifTensor<ORDER> TensorType; typedef FUnifTensor<ORDER> TensorType;
unsigned int node_diff[nnodes*nnodes]; unsigned int node_diff[nnodes*nnodes];
// FDft Dft; // Direct Discrete Fourier Transformator // DFT specific
FFft<1> Dft; // Fast Discrete Fourier Transformator static const int dimfft = 1; // unidim FFT since fully circulant embedding
// FDft Dft; // Direct Discrete Fourier Transformator
typedef FFft<dimfft> DftClass; // Fast Discrete Fourier Transformator
FSmartPointer<DftClass,FSmartPointerMemory> Dft;
const unsigned int opt_rc; // specific to real valued kernel
static const std::string getFileName() static const std::string getFileName()
{ {
...@@ -393,9 +402,12 @@ public: ...@@ -393,9 +402,12 @@ public:
FUnifTensorialM2LHandler(const MatrixKernelClass *const MatrixKernel, const unsigned int inTreeHeight, const FReal inRootCellWidth) FUnifTensorialM2LHandler(const MatrixKernelClass *const MatrixKernel, const unsigned int inTreeHeight, const FReal inRootCellWidth)
: TreeHeight(inTreeHeight), : TreeHeight(inTreeHeight),
RootCellWidth(inRootCellWidth), RootCellWidth(inRootCellWidth),
opt_rc(rc/2+1), opt_rc(rc/2+1)
Dft(rc) // initialize Discrete Fourier Transformator
{ {
// init DFT
const int steps[dimfft] = {rc};
Dft = new DftClass(steps);
// allocate FC // allocate FC
FC = new FComplexe**[TreeHeight]; FC = new FComplexe**[TreeHeight];
for (unsigned int l=0; l<TreeHeight; ++l){ for (unsigned int l=0; l<TreeHeight; ++l){
...@@ -458,7 +470,7 @@ public: ...@@ -458,7 +470,7 @@ public:
FReal Px[rc]; FReal Px[rc];
FBlas::setzero(rc,Px); FBlas::setzero(rc,Px);
// Apply forward Discrete Fourier Transform // Apply forward Discrete Fourier Transform
Dft.applyIDFT(FX,Px); Dft->applyIDFT(FX,Px);
// Unapply Zero Padding // Unapply Zero Padding
for (unsigned int j=0; j<nnodes; ++j) for (unsigned int j=0; j<nnodes; ++j)
...@@ -524,7 +536,7 @@ public: ...@@ -524,7 +536,7 @@ public:
Py[node_diff[i*nnodes]]=y[i]; Py[node_diff[i*nnodes]]=y[i];
// Apply forward Discrete Fourier Transform // Apply forward Discrete Fourier Transform
Dft.applyDFT(Py,FY); Dft->applyDFT(Py,FY);
} }
......
...@@ -70,6 +70,7 @@ extern "C" ...@@ -70,6 +70,7 @@ extern "C"
double*, double*, const unsigned*, int*); double*, double*, const unsigned*, int*);
void dorgqr_(const unsigned*, const unsigned*, const unsigned*, void dorgqr_(const unsigned*, const unsigned*, const unsigned*,
double*, const unsigned*, double*, double*, const unsigned*, int*); double*, const unsigned*, double*, double*, const unsigned*, int*);
void dpotrf_(const char*, const unsigned*, double*, const unsigned*, int*);
#ifdef ScalFMM_USE_MKL_AS_BLAS #ifdef ScalFMM_USE_MKL_AS_BLAS
// mkl: hadamard product is not implemented in mkl_blas // mkl: hadamard product is not implemented in mkl_blas
...@@ -103,6 +104,7 @@ extern "C" ...@@ -103,6 +104,7 @@ extern "C"
float*, float*, const unsigned*, int*); float*, float*, const unsigned*, int*);
void sorgqr_(const unsigned*, const unsigned*, const unsigned*, void sorgqr_(const unsigned*, const unsigned*, const unsigned*,
float*, const unsigned*, float*, float*, const unsigned*, int*); float*, const unsigned*, float*, float*, const unsigned*, int*);
void spotrf_(const char*, const unsigned*, float*, const unsigned*, int*);
// double complex ////////////////////////////////////////////////// // double complex //////////////////////////////////////////////////
// blas 1 // blas 1
...@@ -119,7 +121,7 @@ extern "C" ...@@ -119,7 +121,7 @@ extern "C"
double*, const unsigned*, const double*, double*, const unsigned*); double*, const unsigned*, const double*, double*, const unsigned*);
void zgeqrf_(const unsigned*, const unsigned*, double*, const unsigned*, void zgeqrf_(const unsigned*, const unsigned*, double*, const unsigned*,
double*, double*, const unsigned*, int*); double*, double*, const unsigned*, int*);
void zpotrf_(const char*, const unsigned*, double*, const unsigned*, int*);
// single complex ////////////////////////////////////////////////// // single complex //////////////////////////////////////////////////
// blas 1 // blas 1
...@@ -136,7 +138,7 @@ extern "C" ...@@ -136,7 +138,7 @@ extern "C"
float*, const unsigned*, const float*, float*, const unsigned*); float*, const unsigned*, const float*, float*, const unsigned*);
void cgeqrf_(const unsigned*, const unsigned*, float*, const unsigned*, void cgeqrf_(const unsigned*, const unsigned*, float*, const unsigned*,
float*, float*, const unsigned*, int*); float*, float*, const unsigned*, int*);
void cpotrf_(const char*, const unsigned*, float*, const unsigned*, int*);
} }
...@@ -506,6 +508,19 @@ namespace FBlas { ...@@ -506,6 +508,19 @@ namespace FBlas {
// Cholesky decomposition: A=LL^T (if A is symmetric definite positive)
inline int potrf(const unsigned m, double* A, const unsigned n)
{
int INF;
dpotrf_("L", &m, A, &n, &INF);
return INF;
}
inline int potrf(const unsigned m, float* A, const unsigned n)
{
int INF;
spotrf_("L", &m, A, &n, &INF);
return INF;
}
} // end namespace FCBlas } // end namespace FCBlas
......
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
/** /**
* @author Pierre Blanchard (pierre.blanchard@inria.fr) * @author Pierre Blanchard (pierre.blanchard@inria.fr)
* @class FDft and @class FFft * @class FDft, @class FFft and @class FCFft
* Please read the license * Please read the license
* *
* These classes handle the forward and backward Discete Fourier Transform * These classes handle the forward and backward Discete Fourier Transform
...@@ -45,6 +45,10 @@ ...@@ -45,6 +45,10 @@
* Fourier Transform (FFT). The FFT algorithm can either be provided by the * Fourier Transform (FFT). The FFT algorithm can either be provided by the
* FFTW(3) library itself or a version that is wrapped in Intel MKL. * FFTW(3) library itself or a version that is wrapped in Intel MKL.
* *
* The direct DFT is templatized with the input value type (FReal or FComplexe),
* while 2 distinct classes resp. @class FFft and @class FCFft are defined
* resp. for the real and complex valued FFT.
*
* The aim of writing these specific classes is to allow further customization * The aim of writing these specific classes is to allow further customization
* of the DFT such as implementing a tensorial variant, a weighted variant * of the DFT such as implementing a tensorial variant, a weighted variant
* or any other feature. * or any other feature.
...@@ -59,25 +63,25 @@ ...@@ -59,25 +63,25 @@
* *
* @tparam nsteps number of sampled values \f$N\f$ * @tparam nsteps number of sampled values \f$N\f$
*/ */
template<typename ValueType>
class FDft class FDft
{ {
FReal* fftR_; ValueType* data_; //< data in physical space
FComplexe* fftC_; FComplexe* dataF_; //< data in Fourier space
FReal *cosRS_, *sinRS_; FReal *cosRS_, *sinRS_;
private: private:
unsigned int nsteps_; unsigned int nsteps_; //< number of steps
public: public:
FDft(const unsigned int nsteps) FDft(const unsigned int nsteps)
: nsteps_(nsteps) : nsteps_(nsteps)
{ {
fftR_ = new FReal[nsteps_]; data_ = new ValueType[nsteps_];
fftC_ = new FComplexe[nsteps_]; dataF_ = new FComplexe[nsteps_];
// Beware this is extremely HEAVY to store!!! => Use FDft only for debug! // Beware this is extremely HEAVY to store!!! => Use FDft only for debug!
cosRS_ = new FReal[nsteps_*nsteps_]; cosRS_ = new FReal[nsteps_*nsteps_];
...@@ -93,69 +97,106 @@ public: ...@@ -93,69 +97,106 @@ public:
virtual ~FDft() virtual ~FDft()
{ {
delete [] fftR_; delete [] data_;
delete [] fftC_; delete [] dataF_;
delete [] cosRS_; delete [] cosRS_;
delete [] sinRS_; delete [] sinRS_;
} }
/// Forward DFT
// Real valued DFT
void applyDFT(const FReal* sampledData, void applyDFT(const FReal* sampledData,
FComplexe* transformedData) const FComplexe* transformedData) const
{ {
// FTic time;
// read sampled data // read sampled data
// std::cout<< "copy("; FBlas::c_setzero(nsteps_,reinterpret_cast<FReal*>(dataF_));
// time.tic(); FBlas::copy(nsteps_, sampledData,data_);
FBlas::c_setzero(nsteps_,reinterpret_cast<FReal*>(fftC_));
FBlas::copy(nsteps_, sampledData,fftR_); // perform direct forward transformation
// std::cout << time.tacAndElapsed() << ")";
// std::cout<< " - exe(";
// time.tic();
for(unsigned int r=0; r<nsteps_; ++r) for(unsigned int r=0; r<nsteps_; ++r)
for(unsigned int s=0; s<nsteps_; ++s){ for(unsigned int s=0; s<nsteps_; ++s){
fftC_[r] += FComplexe(fftR_[s]*cosRS_[r*nsteps_+s], dataF_[r] += FComplexe(data_[s]*cosRS_[r*nsteps_+s],
-fftR_[s]*sinRS_[r*nsteps_+s]); -data_[s]*sinRS_[r*nsteps_+s]);
} }
// std::cout << time.tacAndElapsed() << ")";
// write transformed data // write transformed data
// std::cout<< " - copy("; FBlas::c_copy(nsteps_,reinterpret_cast<FReal*>(dataF_),
// time.tic();
FBlas::c_copy(nsteps_,reinterpret_cast<FReal*>(fftC_),
reinterpret_cast<FReal*>(transformedData)); reinterpret_cast<FReal*>(transformedData));
// std::cout << time.tacAndElapsed() << ") "; }
// Complexe valued DFT
void applyDFT(const FComplexe* sampledData,
FComplexe* transformedData) const
{
// read sampled data
FBlas::c_setzero(nsteps_,reinterpret_cast<FReal*>(dataF_));
FBlas::c_copy(nsteps_,reinterpret_cast<const FReal*>(sampledData),
reinterpret_cast<FReal*>(data_));
// perform direct forward transformation
for(unsigned int r=0; r<nsteps_; ++r)
for(unsigned int s=0; s<nsteps_; ++s){
dataF_[r] += FComplexe(data_[s].getReal()*cosRS_[r*nsteps_+s]
+ data_[s].getImag()*sinRS_[r*nsteps_+s],
data_[s].getImag()*cosRS_[r*nsteps_+s]
- data_[s].getReal()*sinRS_[r*nsteps_+s]);
} </