//+------------------------------------------------------------------+
//|                                               alglibinternal.mqh |
//|            Copyright 2003-2022 Sergey Bochkanov (ALGLIB project) |
//|                             Copyright 2012-2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
//| Implementation of ALGLIB library in MetaQuotes Language 5        |
//|                                                                  |
//| The features of the library include:                             |
//| - Linear algebra (direct algorithms, EVD, SVD)                   |
//| - Solving systems of linear and non-linear equations             |
//| - Interpolation                                                  |
//| - Optimization                                                   |
//| - FFT (Fast Fourier Transform)                                   |
//| - Numerical integration                                          |
//| - Linear and nonlinear least-squares fitting                     |
//| - Ordinary differential equations                                |
//| - Computation of special functions                               |
//| - Descriptive statistics and hypothesis testing                  |
//| - Data analysis - classification, regression                     |
//| - Implementing linear algebra algorithms, interpolation, etc.    |
//|   in high-precision arithmetic (using MPFR)                      |
//|                                                                  |
//| This file 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 (www.fsf.org);either   |
//| version 2 of the License, or (at your option) any later version. |
//|                                                                  |
//| This program is distributed in the hope that it will be useful,  |
//| but WITHOUT ANY WARRANTY;without even the implied warranty of    |
//| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the     |
//| GNU General Public License for more details.                     |
//+------------------------------------------------------------------+
#include "ap.mqh"
//+------------------------------------------------------------------+
//| Class stores serialized codes                                    |
//+------------------------------------------------------------------+
class CSCodes
  {
public:
   static int        GetRDFSerializationCode(void)          { return(1);   }
   static int        GetKDTreeSerializationCode(void)       { return(2);   }
   static int        GetMLPSerializationCode(void)          { return(3);   }
   static int        GetMLPESerializationCode(void)         { return(4);   }
   static int        GetRBFSerializationCode(void)          { return(5);   }
   static int        GetSpline2DSerializationCode(void)     { return(6);   }
   static int        GetIDWSerializationCode(void)          { return(7);   }
   static int        GetSparseMatrixSerializationCode(void) { return(8);   }
   static int        GetKNNSerializationCode(void)          { return(108); }
   static int        GetLpTestSerializationCode(void)       { return(200); }
  };
//+------------------------------------------------------------------+
//| Buffers for internal functions which need buffers:               |
//| * check for size of the buffer you want to use.                  |
//| * if buffer is too small, resize it; leave unchanged, if it is   |
//| larger than needed.                                              |
//| * use it.                                                        |
//| We can pass this structure to multiple functions;  after first   |
//| run through functions buffer sizes will be finally determined,   |
//| and  on  a next run no allocation will be required.              |
//+------------------------------------------------------------------+
struct CApBuff
  {
   //--- arrays
   bool              m_ba[];
   CRowInt           m_ia0;
   CRowInt           m_ia1;
   CRowInt           m_ia2;
   CRowInt           m_ia3;
   CRowDouble        m_ra0;
   CRowDouble        m_ra1;
   CRowDouble        m_ra2;
   CRowDouble        m_ra3;
   CMatrixDouble     m_rm0;
   CMatrixDouble     m_rm1;
   //--- constructor, destructor
                     CApBuff(void) {}
                    ~CApBuff(void) {}
   //--- copy
   void              Copy(const CApBuff &obj);
   //--- overloading
   void              operator=(const CApBuff &buf) { Copy(buf); }
  };
//+------------------------------------------------------------------+
//| Copy                                                             |
//+------------------------------------------------------------------+
void CApBuff::Copy(const CApBuff &obj)
  {
//--- copy arrays
   m_ia0=obj.m_ia0;
   m_ia1=obj.m_ia1;
   m_ia2=obj.m_ia2;
   m_ia3=obj.m_ia3;
   m_ra0=obj.m_ra0;
   m_ra1=obj.m_ra1;
   m_ra2=obj.m_ra2;
   m_ra3=obj.m_ra3;
   m_rm0=obj.m_rm0;
   m_rm1=obj.m_rm1;
   ArrayFree(m_ba);
   ArrayCopy(m_ba,obj.m_ba);
  }
//+------------------------------------------------------------------+
//| Basic functions                                                  |
//+------------------------------------------------------------------+
class CApServ
  {
public:
   //--- constants
   static double     SparseLevel2Density(void);
   static int        MatrixTileSizeA(void);
   static int        MatrixTileSizeB(void);
   static double     SMPActivationLevel(void);
   static double     SpawnLevel(void);

   //--- generate interpolation
   static void       TaskGenInt1D(const double a,const double b,const int n,double &x[],double &y[]);
   static void       TaskGenInt1D(const double a,const double b,const int n,CRowDouble &x,CRowDouble &y);
   static void       TaskGenInt1DEquidist(const double a,const double b,const int n,double &x[],double &y[]);
   static void       TaskGenInt1DEquidist(const double a,const double b,const int n,CRowDouble &x,CRowDouble &y);
   static void       TaskGenInt1DCheb1(const double a,const double b,const int n,double &x[],double &y[]);
   static void       TaskGenInt1DCheb1(const double a,const double b,const int n,CRowDouble &x,CRowDouble &y);
   static void       TaskGenInt1DCheb2(const double a,const double b,const int n,double &x[],double &y[]);
   static void       TaskGenInt1DCheb2(const double a,const double b,const int n,CRowDouble &x,CRowDouble &y);
   //--- distinct
   static bool       AreDistinct(double &x[],const int n);
   static bool       AreDistinct(CRowDouble &x,const int n);
   //--- resize arrays
   static void       BVectorSetLengthAtLeast(bool &x[],const int n);
   static void       IVectorSetLengthAtLeast(int &x[],const int n);
   static void       IVectorSetLengthAtLeast(CRowInt &x,const int n);
   static bool       RVectorSetLengthAtLeast(double &x[],const int n);
   template <typename T>
   static bool       RVectorSetLengthAtLeast(vector<T> &x,const int n);
   static bool       RVectorSetLengthAtLeast(CRowDouble &x,const int n);
   template <typename T>
   static void       VectorGrowTo(T &x[],int n);
   static void       VectorGrowTo(CRowInt &x,int n);
   static void       VectorGrowTo(CRowDouble &x,int n);
   template <typename T>
   static void       VectorAppend(T &x[],T v);
   static void       VectorAppend(CRowInt &x,int v);
   static void       VectorAppend(CRowDouble &x,double v);
   static void       VectorAppend(CRowComplex &x,complex v);
   template <typename T>
   static void       UnsetArray(T &a[])   {  ArrayFree(a);  }
   //--- resize matrix
   static bool       RMatrixSetLengthAtLeast(CMatrixDouble &x,const int m,const int n);
   static void       RMatrixResize(CMatrixDouble &x,const int m,const int n);
   static void       RMatrixGrowRowsTo(CMatrixDouble &a,int n,int mincols);
   static void       RMatrixGrowColsTo(CMatrixDouble &a,int n,int minrows);

   //--- check to infinity
   static bool       IsFiniteVector(const double &x[],const int n);
   static bool       IsFiniteVector(const CRowDouble &x,const int n);
   static bool       IsFiniteComplexVector(CRowComplex &x,const int n);
   static bool       IsFiniteComplexVector(complex &z[],const int n);
   static bool       IsFiniteMatrix(const CMatrixDouble &x,const int m,const int n);
   static bool       IsFiniteComplexMatrix(CMatrixComplex &x,const int m,const int n);
   static bool       IsFiniteRTrMatrix(CMatrixDouble &x,const int n,const bool IsUpper);
   static bool       IsFiniteCTrMatrix(CMatrixComplex &x,const int n,const bool IsUpper);
   static bool       IsFiniteOrNaNMatrix(CMatrixDouble &x,const int m,const int n);
   //--- safe methods
   static double     SafePythag2(const double x,const double y);
   static double     SafePythag3(double x,double y,double z);
   static int        SafeRDiv(double x,double y,double &r);
   static double     SafeMinPosRV(const double x,const double y,const double v);
   static void       ApPeriodicMap(double &x,const double a,const double b,double &k);
   static double     RandomNormal(void);
   static void       RandomUnit(int n,CRowDouble &x);
   template <typename T>
   static T          BoundVal(const T x,const T b1,const T b2);
   static void       CountDown(int &v);
   static double     PosSign(double x);
   template <typename T>
   static T          RMaxAbs3(T r0,T r1,T r2);

   //--- swaps
   template <typename T>
   static void       Swap(T &v0,T &v1);
   static void       SwapRows(CMatrixDouble &a,int i0,int i1,int ncols);
   static void       SwapCols(CMatrixDouble &a,int j0,int j1,int nrows);
   static void       SwapEntries(CRowDouble &a,int i0,int i1,int entrywidth);
   static void       SwapElements(CRowDouble &a,int i0,int i1);
   static void       SwapElementsI(CRowInt &a,int i0,int i1);
   static int        IDivUp(int a,int b);

   //--- serialization/unserialization
   static void       AllocComplex(CSerializer &s,complex &v);
   static void       SerializeComplex(CSerializer &s,complex &v);
   static complex    UnserializeComplex(CSerializer &s);
   static void       AllocRealArray(CSerializer &s,double &v[],int n=-1);
   static void       AllocRealArray(CSerializer &s,CRowDouble &v,int n=-1);
   static void       SerializeRealArray(CSerializer &s,double &v[],int n=-1);
   static void       SerializeRealArray(CSerializer &s,CRowDouble &v,int n=-1);
   static void       UnserializeRealArray(CSerializer &s,double &v[]);
   static void       UnserializeRealArray(CSerializer &s,CRowDouble &v);
   static void       AllocIntegerArray(CSerializer &s,int &v[],int n=-1);
   static void       AllocIntegerArray(CSerializer &s,CRowInt &v,int n=-1);
   static void       SerializeIntegerArray(CSerializer &s,int &v[],int n=-1);
   static void       SerializeIntegerArray(CSerializer &s,CRowInt &v,int n=-1);
   static void       UnserializeIntegerArray(CSerializer &s,int &v[]);
   static void       UnserializeIntegerArray(CSerializer &s,CRowInt &v);
   static void       AllocBoolArray(CSerializer &s,bool &v[],int n=-1);
   static void       SerializeBoolArray(CSerializer &s,bool &v[],int n=-1);
   static void       AllocRealMatrix(CSerializer &s,CMatrixDouble &v,int n0,int n1);
   static void       SerializeRealMatrix(CSerializer &s,CMatrixDouble &v,int n0,int n1);
   static void       UnserializeRealMatrix(CSerializer &s,CMatrixDouble &v);
   //--- copy
   static void       CopyIntegerArray(int &src[],int &dst[]);
   static void       CopyIntegerArray(CRowInt &src,CRowInt &dst);
   static void       CopyRealArray(double &src[],double &dst[]);
   static void       CopyRealArray(double &src[],CRowDouble &dst);
   static void       CopyRealArray(CRowDouble &src,CRowDouble &dst);
   static void       CopyRealMatrix(CMatrixDouble &src,CMatrixDouble &dst);
   //--- split
   static void       TiledSplit(int tasksize,int tilesize,int &task0,int &task1);
   static void       SplitLengthEven(int tasksize,int &task0,int &task1);
   static void       SplitLength(int tasksize,int m_ChunkSize,int &task0,int &task1);

   //--- check array
   static int        RecSearch(int &a[],const int nrec,const int nheader,int i0,int i1,int &b[]);
   static int        RecSearch(CRowInt &a,const int nrec,const int nheader,int i0,int i1,CRowInt &b);

   static int        CountNZ1(CRowDouble &v,int n);
   static int        CountNZ2(CMatrixDouble &v,int m,int n);
   //---
   static int        ChunksCount(int tasksize,int m_ChunkSize);
   static double     Coalesce(double a,double b);
   static int        CoalesceI(int a,int b);
   static double     LogBase2(double x);
   static bool       ApproxEqual(double a,double b,double tol);
   static bool       ApproxEqualRel(double a,double b,double tol);
   //--- trace
   static void       TraceVectorAutopRec(CRowDouble &a,int i0,int i1);
   static void       TraceRowAutopRec(CMatrixDouble &a,int i,int j0,int j1);
   static void       TraceVectoRunScaledUnshiftedAutopRec(CRowDouble &x,int n,CRowDouble &scl,bool applyscl,CRowDouble &sft,bool applysft);
   static void       TraceVectorUnscaledUnshiftedAutopRec(CRowDouble &x,int n,CRowDouble &scl,bool applyscl,CRowDouble &sft,bool applysft);
   static void       TraceRowNrm1AutopRec(CMatrixDouble &a,int i0,int i1,int j0,int j1);
   static void       TraceVectorE3(CRowDouble &a,int i0,int i1);
   static void       TraceVectorE6(CRowDouble &a,int i0,int i1);
   static void       TraceVectorE615(CRowDouble &a,int i0,int i1,bool usee15);
   static void       TraceRowNrm1E6(CMatrixDouble &a,int i0,int i1,int j0,int j1);
  };
//+------------------------------------------------------------------+
//| This  function  generates  1-dimensional  general  interpolation |
//| task with moderate Lipshitz constant (close to 1.0)              |
//| If N=1 then suborutine generates only one point at the middle    |
//| of [A,B]                                                         |
//+------------------------------------------------------------------+
void CApServ::TaskGenInt1D(const double a,const double b,const int n,
                           double &x[],double &y[])
  {
//--- create variables
   int    i=0;
   double h=0;
//--- check
   if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
      return;
//--- allocation
   ArrayResize(x,n);
   ArrayResize(y,n);
//--- check
   if(n>1)
     {
      //--- change values
      x[0]=a;
      y[0]=2*CMath::RandomReal()-1;
      h=(b-a)/(n-1);
      for(i=1; i<n; i++)
        {
         //--- check
         if(i!=n-1)
            x[i]=a+(i+0.2*(2*CMath::RandomReal()-1))*h;
         else
            x[i]=b;
         y[i]=y[i-1]+(2*CMath::RandomReal()-1)*(x[i]-x[i-1]);
        }
     }
   else
     {
      //--- change values
      x[0]=0.5*(a+b);
      y[0]=2*CMath::RandomReal()-1;
     }
  }
//+------------------------------------------------------------------+
//| This  function  generates  1-dimensional  general  interpolation |
//| task with moderate Lipshitz constant (close to 1.0)              |
//| If N=1 then suborutine generates only one point at the middle    |
//| of [A,B]                                                         |
//+------------------------------------------------------------------+
void CApServ::TaskGenInt1D(const double a,const double b,const int n,
                           CRowDouble &x,CRowDouble &y)
  {
//--- create variables
   int    i=0;
   double h=0;
//--- check
   if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
      return;
//--- allocation
   y.Resize(n);
//--- check
   if(n>1)
     {
      //--- change values
      matrix<double> m;
      m.Init(1,n);
      m.Random(-0.2,0.2);
      h=(b-a)/(n-1);
      x=vector<double>::Ones(n).CumSum()-1;
      x=(x+m.Row(0))*h;
      x.Set(0,a);
      x.Set(n-1,b);
      y.Set(0,2*CMath::RandomReal()-1);
      for(i=1; i<n; i++)
         y.Set(i,y[i-1]+(2*CMath::RandomReal()-1)*(x[i]-x[i-1]));
     }
   else
     {
      //--- change values
      x.Set(0,0.5*(a+b));
      y.Set(0,2*CMath::RandomReal()-1);
     }
  }
//+------------------------------------------------------------------+
//| This function generates  1-dimensional equidistant interpolation |
//| task withmoderate Lipshitz constant(close to 1.0)                |
//| If N=1 then suborutine generates only one point at the middle    |
//| of[A,B]                                                          |
//+------------------------------------------------------------------+
void CApServ::TaskGenInt1DEquidist(const double a,const double b,
                                   const int n,double &x[],double &y[])
  {
//--- create variables
   int    i=0;
   double h=0;
//--- check
   if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
      return;
//--- allocation
   ArrayResize(x,n);
   ArrayResize(y,n);
//--- check
   if(n>1)
     {
      //--- change values
      x[0]=a;
      y[0]=2*CMath::RandomReal()-1;
      h=(b-a)/(n-1);
      for(i=1; i<n; i++)
        {
         x[i]=a+i*h;
         y[i]=y[i-1]+(2*CMath::RandomReal()-1)*h;
        }
     }
   else
     {
      //--- change values
      x[0]=0.5*(a+b);
      y[0]=2*CMath::RandomReal()-1;
     }
  }
//+------------------------------------------------------------------+
//| This function generates  1-dimensional equidistant interpolation |
//| task withmoderate Lipshitz constant(close to 1.0)                |
//| If N=1 then suborutine generates only one point at the middle    |
//| of[A,B]                                                          |
//+------------------------------------------------------------------+
void CApServ::TaskGenInt1DEquidist(const double a,const double b,const int n,
                                   CRowDouble &x,CRowDouble &y)
  {
//--- create variables
   int    i=0;
   double h=0;
//--- check
   if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
      return;
//--- allocation
   y.Resize(n);
//--- check
   if(n>1)
     {
      //--- change values
      h=(b-a)/(n-1);
      vector<double> temp=vector<double>::Ones(n).CumSum()-1;
      x=temp*h+a;
      y.Set(0,(2*CMath::RandomReal()-1));
      for(i=1; i<n; i++)
         y.Set(i,(y[i-1]+(2*CMath::RandomReal()-1)*h));
     }
   else
     {
      //--- change values
      x.Set(0,0.5*(a+b));
      y.Set(0,2*CMath::RandomReal()-1);
     }
  }
//+------------------------------------------------------------------+
//| This function generates  1-dimensional Chebyshev-1 interpolation |
//| task with moderate Lipshitz constant(close to 1.0)               |
//| If N=1 then suborutine generates only one point at the middle    |
//| of[A,B]                                                          |
//+------------------------------------------------------------------+
void CApServ::TaskGenInt1DCheb1(const double a,const double b,
                                const int n,double &x[],double &y[])
  {
//--- check
   if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
      return;
//--- allocation
   ArrayResize(x,n);
   ArrayResize(y,n);
//--- check
   if(n>1)
     {
      for(int i=0; i<n; i++)
        {
         x[i]=0.5*(b+a)+0.5*(b-a)*MathCos(M_PI*(i+0.5)/(double)n);
         //--- check
         if(i==0)
            y[i]=2*CMath::RandomReal()-1;
         else
            y[i]=y[i-1]+(2*CMath::RandomReal()-1)*(x[i]-x[i-1]);
        }
     }
   else
     {
      //--- change values
      x[0]=0.5*(a+b);
      y[0]=2*CMath::RandomReal()-1;
     }
  }
//+------------------------------------------------------------------+
//| This function generates  1-dimensional Chebyshev-1 interpolation |
//| task with moderate Lipshitz constant(close to 1.0)               |
//| If N=1 then suborutine generates only one point at the middle    |
//| of[A,B]                                                          |
//+------------------------------------------------------------------+
void CApServ::TaskGenInt1DCheb1(const double a,const double b,const int n,
                                CRowDouble &x,CRowDouble &y)
  {
//--- check
   if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
      return;
//--- allocation
   y.Resize(n);
//--- check
   if(n>1)
     {
      vector<double> temp=vector<double>::Ones(n).CumSum()-0.5;
      x=MathCos(temp*M_PI/(double)n)*0.5*(b-a)+0.5*(b+a);
      y.Set(0,(2*CMath::RandomReal()-1));
      for(int i=1; i<n; i++)
         y.Set(i,(y[i-1]+(2*CMath::RandomReal()-1)*(x[i]-x[i-1])));
     }
   else
     {
      //--- change values
      x.Set(0,(0.5*(a+b)));
      y.Set(0,(2*CMath::RandomReal()-1));
     }
  }
//+------------------------------------------------------------------+
//| This function generates  1-dimensional Chebyshev-2 interpolation |
//| task with moderate Lipshitz constant(close to 1.0)               |
//| If N=1 then suborutine generates only one point at the middle    |
//| of[A,B]                                                          |
//+------------------------------------------------------------------+
void CApServ::TaskGenInt1DCheb2(const double a,const double b,
                                const int n,double &x[],double &y[])
  {
//--- check
   if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
      return;
//--- allocation
   ArrayResize(x,n);
   ArrayResize(y,n);
//--- check
   if(n>1)
     {
      for(int i=0; i<n; i++)
        {
         x[i]=0.5*(b+a)+0.5*(b-a)*MathCos(M_PI*i/(n-1));
         //--- check
         if(i==0)
            y[i]=2*CMath::RandomReal()-1;
         else
            y[i]=y[i-1]+(2*CMath::RandomReal()-1)*(x[i]-x[i-1]);
        }
     }
   else
     {
      //--- change values
      x[0]=0.5*(a+b);
      y[0]=2*CMath::RandomReal()-1;
     }
  }
//+------------------------------------------------------------------+
//| This function generates  1-dimensional Chebyshev-2 interpolation |
//| task with moderate Lipshitz constant(close to 1.0)               |
//| If N=1 then suborutine generates only one point at the middle    |
//| of[A,B]                                                          |
//+------------------------------------------------------------------+
void CApServ::TaskGenInt1DCheb2(const double a,const double b,const int n,
                                CRowDouble &x,CRowDouble &y)
  {
//--- check
   if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
      return;
//--- allocation
   y.Resize(n);
//--- check
   if(n>1)
     {
      vector<double> temp=vector<double>::Ones(n).CumSum();
      x=MathCos(temp*M_PI/(n-1.0))*0.5*(b-a)+0.5*(b+a);
      y.Set(0,(2*CMath::RandomReal()-1));
      for(int i=1; i<n; i++)
         y.Set(i,(y[i-1]+(2*CMath::RandomReal()-1)*(x[i]-x[i-1])));
     }
   else
     {
      //--- change values
      x.Set(0,(0.5*(a+b)));
      y.Set(0,(2*CMath::RandomReal()-1));
     }
  }
//+------------------------------------------------------------------+
//| This function checks that all values from X[] are distinct.      |
//| It does more than just usual floating point comparison:          |
//| * first, it calculates max(X) and min(X)                         |
//| * second, it maps X[] from [min,max] to [1,2]                    |
//| * only at this stage actual comparison is done                   |
//| The meaning of such check is to ensure that all values are       |
//| "distinct enough" and will not cause interpolation subroutine    |
//| to fail.                                                         |
//|  NOTE:                                                           |
//|     X[] must be sorted by ascending (subroutine ASSERT's it)     |
//+------------------------------------------------------------------+
bool CApServ::AreDistinct(double &x[],const int n)
  {
//--- check
   if(!CAp::Assert(n>=1,__FUNCTION__+": internal error (N<1)"))
      return(false);
//--- check
   if(n==1)
     {
      //--- everything is alright, it is up to caller to decide whether it
      //--- can interpolate something with just one point
      return(true);
     }
//--- create variables
   double a=0;
   double b=0;
   int    i=0;
   bool   nonsorted;
//--- initialization
   a=x[0];
   b=x[0];
   nonsorted=false;
   for(i=1; i<n; i++)
     {
      a=MathMin(a,x[i]);
      b=MathMax(b,x[i]);
      nonsorted=nonsorted || x[i-1]>=x[i];
     }
//--- check
   if(!CAp::Assert(!nonsorted,__FUNCTION__+": internal error (not sorted)"))
      return(false);
   for(i=1; i<n; i++)
     {
      //--- check
      if((x[i]-a)/(b-a)==(x[i-1]-a)/(b-a))
         return(false);
     }
//--- return result
   return(true);
  }
//+------------------------------------------------------------------+
//| This function checks that all values from X[] are distinct.      |
//| It does more than just usual floating point comparison:          |
//| * first, it calculates max(X) and min(X)                         |
//| * second, it maps X[] from [min,max] to [1,2]                    |
//| * only at this stage actual comparison is done                   |
//| The meaning of such check is to ensure that all values are       |
//| "distinct enough" and will not cause interpolation subroutine    |
//| to fail.                                                         |
//|  NOTE:                                                           |
//|     X[] must be sorted by ascending (subroutine ASSERT's it)     |
//+------------------------------------------------------------------+
bool CApServ::AreDistinct(CRowDouble &x,const int n)
  {
//--- check
   if(!CAp::Assert(n>=1,__FUNCTION__+": internal error (N<1)"))
      return(false);
//--- check
   if(n==1)
     {
      //--- everything is alright, it is up to caller to decide whether it
      //--- can interpolate something with just one point
      return(true);
     }
//--- create variables
   double a=0;
   double b=0;
   int    i=0;
   bool   nonsorted;
//--- initialization
   a=x.Min();
   b=x.Max();
   nonsorted=false;
   for(i=1; i<n; i++)
      nonsorted=nonsorted || x[i-1]>=x[i];
//--- check
   if(!CAp::Assert(!nonsorted,__FUNCTION__+": internal error (not sorted)"))
      return(false);
   for(i=1; i<n; i++)
     {
      //--- check
      if((x[i]-a)/(b-a)==(x[i-1]-a)/(b-a))
         return(false);
     }
//--- return result
   return(true);
  }
//+------------------------------------------------------------------+
//| If Length(X)<N, resizes X                                        |
//+------------------------------------------------------------------+
void CApServ::BVectorSetLengthAtLeast(bool &x[],const int n)
  {
//--- check
   if(CAp::Len(x)<n)
      ArrayResizeAL(x,n);
  }
//+------------------------------------------------------------------+
//| If Length(X)<N, resizes X                                        |
//+------------------------------------------------------------------+
void CApServ::IVectorSetLengthAtLeast(CRowInt &x,const int n)
  {
//--- check
   if(x.Size()<n)
      x.Resize(n);
  }
//+------------------------------------------------------------------+
//| If Length(X)<N, resizes X                                        |
//+------------------------------------------------------------------+
void CApServ::IVectorSetLengthAtLeast(int &x[],const int n)
  {
//--- check
   if(CAp::Len(x)<n)
      ArrayResizeAL(x,n);
  }
//+------------------------------------------------------------------+
//| If Length(X)<N , resizes X                                       |
//+------------------------------------------------------------------+
bool CApServ::RVectorSetLengthAtLeast(double &x[],const int n)
  {
//--- check
   if(CAp::Len(x)<n)
      if(ArrayResize(x,n)<0)
         return(false);

   return(true);
  }
//+------------------------------------------------------------------+
//| If Length(X)<N , resizes X                                       |
//+------------------------------------------------------------------+
template <typename T>
bool CApServ::RVectorSetLengthAtLeast(vector<T> &x,const int n)
  {
//--- check
   if((int)x.Size()<n)
      if(!x.Resize(n))
         return(false);

   return(true);
  }
//+------------------------------------------------------------------+
//| If Length(X)<N , resizes X                                       |
//+------------------------------------------------------------------+
bool CApServ::RVectorSetLengthAtLeast(CRowDouble &x,const int n)
  {
//--- check
   if((int)x.Size()<n)
      if(!x.Resize(n))
         return(false);

   return(true);
  }
//+------------------------------------------------------------------+
//| Grows X, i.e. changes its size in such a way that:               |
//|   a) contents is preserved                                       |
//|   b) new size is at least N                                      |
//|   c) new size can be larger than N, so subsequent grow() calls   |
//|      can return without reallocation                             |
//+------------------------------------------------------------------+
template <typename T>
void CApServ::VectorGrowTo(T &x[],int n)
  {
//--- create variables
   int i=0;
   int n2=0;
//--- Enough place
   if(CAp::Len(x)>=n)
      return;
//--- Choose new size
   n=MathMax(n,(int)MathRound(1.8*x.Size()+1));
//--- Grow
   n2=x.Size();
   if(ArrayResize(x,n)<n)
      return;
   T def=(T)0;
   for(i=n2; i<n; i++)
      x[i]=def;
  }
//+------------------------------------------------------------------+
//| Same                                                             |
//+------------------------------------------------------------------+
void CApServ::VectorGrowTo(CRowInt &x,int n)
  {
//--- create variables
   int i=0;
   int n2=0;
//--- Enough place
   if(x.Size()>=n)
      return;
//--- Choose new size
   n=MathMax(n,(int)MathRound(1.8*x.Size()+1));
//--- Grow
   n2=x.Size();
   x.Resize(n);
   for(i=n2; i<n; i++)
      x.Set(i,0);
  }
//+------------------------------------------------------------------+
//| Same                                                             |
//+------------------------------------------------------------------+
void CApServ::VectorGrowTo(CRowDouble &x,int n)
  {
//--- create variables
   int i=0;
   int n2=0;
//--- Enough place
   if((int)x.Size()>=n)
      return;
//--- Choose new size
   n=MathMax(n,(int)MathRound(1.8*x.Size()+1));
//--- Grow
   n2=(int)x.Size();
   x.Resize(n);
   for(i=n2; i<n; i++)
      x.Set(i,0.0);
  }
//+------------------------------------------------------------------+
//| If Cols(X)<N or Rows(X)<M, resizes X                             |
//+------------------------------------------------------------------+
bool CApServ::RMatrixSetLengthAtLeast(CMatrixDouble &x,const int m,
                                      const int n)
  {
//--- check
   if((int)CAp::Rows(x)<m || (int)CAp::Cols(x)<n)
      if(!x.Resize(m,n))
         return(false);

   return(true);
  }
//+------------------------------------------------------------------+
//| Resizes X and:                                                   |
//| * preserves old contents of X                                    |
//| * fills new elements by zeros                                    |
//+------------------------------------------------------------------+
void CApServ::RMatrixResize(CMatrixDouble &x,const int m,const int n)
  {
//--- initialization
   ulong m2=x.Rows();
   ulong n2=x.Cols();
//--- resize
   x.Resize(m,n);
//--- filling
   vector<double> zero=vector<double>::Zeros(n);
   for(int i=(int)m2; i<m; i++)
      x.Row(i,zero);
   zero=vector<double>::Zeros(m);
   for(int i=(int)n2; i<n; i++)
      x.Col(i,zero);
  }
//+------------------------------------------------------------------+
//| Grows X, i.e. appends rows in such a way that:                   |
//|   a) contents is preserved                                       |
//|   b) new row count is at least N                                 |
//|   c) new row count can be larger than N, so subsequent grow()    |
//|      calls can return without reallocation                       |
//|   d) new matrix has at least MinCols columns (if less than       |
//|      specified amount of columns is present, new columns are     |
//|      added with undefined contents);                             |
//| MinCols can be 0 or negative value = ignored                     |
//+------------------------------------------------------------------+
void CApServ::RMatrixGrowRowsTo(CMatrixDouble &a,int n,int mincols)
  {
//--- Enough place?
   if(a.Rows()>=n && a.Cols()>=mincols)
      return;
//--- Sizes and metrics
   if(a.Rows()<n)
      n=MathMax(n,(int)MathRound(1.8*a.Rows()+1));
   int m=a.Cols();
//--- Grow
   a.Resize(n,MathMax(m,mincols));
  }
//+------------------------------------------------------------------+
//| Grows X, i.e. appends cols in such a way that:                   |
//|   a) contents is preserved                                       |
//|   b) new col count is at least N                                 |
//|   c) new col count can be larger than N, so subsequent grow()    |
//|      calls can return without reallocation                       |
//|   d) new matrix has at least MinRows row (if less than specified |
//|      amount of rows is present, new rows are added with undefined|
//|      contents);                                                  |
//| MinRows can be 0 or negative value = ignored                     |
//+------------------------------------------------------------------+
void CApServ::RMatrixGrowColsTo(CMatrixDouble &a,int n,int minrows)
  {
//--- Enough place?
   if(a.Cols()>=n && a.Rows()>=minrows)
      return;
//--- Sizes and metrics
   if(a.Cols()<n)
      n=MathMax(n,(int)MathRound(1.8*a.Cols()+1));
   int m=a.Rows();
//--- Grow
   a.Resize(MathMax(m,minrows),n);
  }
//+------------------------------------------------------------------+
//| Appends element to X                                             |
//+------------------------------------------------------------------+
template <typename T>
void CApServ::VectorAppend(T &x[],T v)
  {
   int n=x.Size();
   if(ArrayResize(x,n+1)<(n+1))
      return;
   x[n]=v;
  }
//+------------------------------------------------------------------+
//| Appends element to X                                             |
//+------------------------------------------------------------------+
void CApServ::VectorAppend(CRowInt &x,int v)
  {
   int n=x.Size();
   x.Resize(n+1);
   x.Set(n,v);
  }
//+------------------------------------------------------------------+
//| Appends element to X                                             |
//+------------------------------------------------------------------+
void CApServ::VectorAppend(CRowDouble &x,double v)
  {
   int n=(int)x.Size();
   x.Resize(n+1);
   x.Set(n,v);
  }
//+------------------------------------------------------------------+
//| Appends element to X                                             |
//+------------------------------------------------------------------+
void CApServ::VectorAppend(CRowComplex &x,complex v)
  {
   int n=(int)x.Size();
   x.Resize(n+1);
   x.Set(n,v);
  }
//+------------------------------------------------------------------+
//| This function checks that all values from X[] are finite         |
//+------------------------------------------------------------------+
bool CApServ::IsFiniteComplexVector(complex &x[],const int n)
  {
//--- check
   if(!CAp::Assert(n>=0,__FUNCTION__+": internal error (N<0)"))
      return(false);

   if(n==0)
      return(true);
   if((int)x.Size()<n)
      return(false);

   for(int i=0; i<n; i++)
     {
      //--- check
      if(!CMath::IsFinite(x[i].real) || !CMath::IsFinite(x[i].imag))
         return(false);
     }
//--- return result
   return(true);
  }
//+------------------------------------------------------------------+
//| This function checks that length(X) is at least N and first N    |
//| values from X[] are finite                                       |
//+------------------------------------------------------------------+
bool CApServ::IsFiniteVector(const double &x[],const int n)
  {
//--- check
   if(!CAp::Assert(n>=0,__FUNCTION__+": the error variable"))
      return(false);

   if(n==0)
      return(true);
   if((int)x.Size()<n)
      return(false);
//--- is finite?
   for(int i=0; i<n; i++)
      if(!CMath::IsFinite(x[i]))
         return(false);
//--- is finite
   return(true);
  }
//+------------------------------------------------------------------+
//| This function checks that length(X) is at least N and first N    |
//| values from X[] are finite                                       |
//+------------------------------------------------------------------+
bool CApServ::IsFiniteVector(const CRowDouble &x,const int n)
  {
//--- check
   if(!CAp::Assert(n>=0,__FUNCTION__+": the error variable"))
      return(false);

   if(n==0)
      return(true);
   if((int)x.Size()<n)
      return(false);
//--- is finite?
   for(int i=0; i<n; i++)
      if(!CMath::IsFinite(x[i]))
         return(false);
//--- is finite
   return(true);
  }
//+------------------------------------------------------------------+
//| This function checks that length(X) is at least N and first N    |
//| values from X[] are finite                                       |
//+------------------------------------------------------------------+
bool CApServ::IsFiniteComplexVector(CRowComplex &x,const int n)
  {
//--- check
   if(!CAp::Assert(n>=0,__FUNCTION__+": the error variable"))
      return(false);

   if(n==0)
      return(true);
   if((int)x.Size()<n)
      return(false);
//--- is finite?
   for(int i=0; i<n; i++)
      if(!CMath::IsFinite(x[i].real) || !CMath::IsFinite(x[i].imag))
         return(false);
//--- is finite
   return(true);
  }
//+------------------------------------------------------------------+
//| This function checks that all values from X[0..M-1,0..N-1]       |
//| are finite                                                       |
//+------------------------------------------------------------------+
bool CApServ::IsFiniteMatrix(const CMatrixDouble &x,const int m,
                             const int n)
  {
//--- check
   if(!CAp::Assert(n>=0,__FUNCTION__+": the error variable"))
      return(false);
//--- check
   if(!CAp::Assert(m>=0,__FUNCTION__+": the error variable"))
      return(false);

   if(m==0 || n==0)
      return(true);
   if((int)x.Rows()<m || (int)x.Cols()<n)
      return(false);
//--- is finite?
   for(int i=0; i<m; i++)
      for(int j=0; j<n; j++)
         //--- check
         if(!CMath::IsFinite(x.Get(i,j)))
            return(false);
//--- is finite
   return(true);
  }
//+------------------------------------------------------------------+
//| This function checks that all values from X[0..M-1,0..N-1]       |
//| are finite                                                       |
//+------------------------------------------------------------------+
bool CApServ::IsFiniteComplexMatrix(CMatrixComplex &x,const int m,
                                    const int n)
  {
//--- check
   if(!CAp::Assert(n>=0,__FUNCTION__+": internal error (N<0)"))
      return(false);
//--- check
   if(!CAp::Assert(m>=0,__FUNCTION__+": internal error (M<0)"))
      return(false);

   if(m==0 || n==0)
      return(true);
   if((int)x.Rows()<m || (int)x.Cols()<n)
      return(false);
//--- is finite?
   for(int i=0; i<m; i++)
     {
      for(int j=0; j<n; j++)
         //--- check
         if(!CMath::IsFinite(x.Get(i,j).real) || !CMath::IsFinite(x.Get(i,j).imag))
            return(false);
     }
//--- is finite
   return(true);
  }
//+------------------------------------------------------------------+
//| This function checks that all values from upper/lower triangle of|
//| X[0..N-1,0..N-1] are finite                                      |
//+------------------------------------------------------------------+
bool CApServ::IsFiniteRTrMatrix(CMatrixDouble &x,const int n,
                                const bool IsUpper)
  {
//--- create variables
   int i=0;
   int j1=0;
   int j2=0;
   int j=0;
//--- check
   if(!CAp::Assert(n>=0,__FUNCTION__+": internal error (N<0)"))
      return(false);

   if(n==0)
      return(true);
   if((int)x.Rows()<n || (int)x.Cols()<n)
      return(false);

   for(i=0; i<n; i++)
     {
      //--- check
      if(IsUpper)
        {
         j1=i;
         j2=n-1;
        }
      else
        {
         j1=0;
         j2=i;
        }
      for(j=j1; j<=j2; j++)
        {
         //--- check
         if(!CMath::IsFinite(x.Get(i,j)))
            return(false);
        }
     }
//--- return result
   return(true);
  }
//+------------------------------------------------------------------+
//| This function checks that all values from upper/lower triangle of|
//| X[0..N-1,0..N-1] are finite                                      |
//+------------------------------------------------------------------+
bool CApServ::IsFiniteCTrMatrix(CMatrixComplex &x,const int n,
                                const bool IsUpper)
  {
//--- create variables
   int i=0;
   int j1=0;
   int j2=0;
   int j=0;
//--- check
   if(!CAp::Assert(n>=0,__FUNCTION__+": internal error (N<0)"))
      return(false);

   if(n==0)
      return(true);
   if((int)x.Rows()<n || (int)x.Cols()<n)
      return(false);

   for(i=0; i<n; i++)
     {
      //--- check
      if(IsUpper)
        {
         j1=i;
         j2=n-1;
        }
      else
        {
         j1=0;
         j2=i;
        }
      for(j=j1; j<=j2; j++)
        {
         //--- check
         if(!CMath::IsFinite(x.Get(i,j).real) || !CMath::IsFinite(x.Get(i,j).imag))
            return(false);
        }
     }
//--- return result
   return(true);
  }
//+------------------------------------------------------------------+
//| This function checks that all values from X[0..M-1,0..N-1] are   |
//| finite or NaN's.                                                 |
//+------------------------------------------------------------------+
bool CApServ::IsFiniteOrNaNMatrix(CMatrixDouble &x,const int m,
                                  const int n)
  {
//--- check
   if(!CAp::Assert(n>=0,__FUNCTION__+": internal error (N<0)"))
      return(false);
//--- check
   if(!CAp::Assert(m>=0,__FUNCTION__+": internal error (M<0)"))
      return(false);

   if(m==0 || n==0)
      return(true);
   if((int)x.Rows()<m || (int)x.Cols()<n)
      return(false);

   for(int i=0; i<m; i++)
     {
      for(int j=0; j<n; j++)
        {
         //--- check
         if(!(CMath::IsFinite(x.Get(i,j)) || CInfOrNaN::IsNaN(x.Get(i,j))))
            return(false);
        }
     }
//--- return result
   return(true);
  }
//+------------------------------------------------------------------+
//| Safe sqrt(x^2+y^2)                                               |
//+------------------------------------------------------------------+
double CApServ::SafePythag2(const double x,const double y)
  {
//--- create variables
   double result=0;
   double xabs=MathAbs(x);
   double yabs=MathAbs(y);
   double w=MathMax(xabs,yabs);
   double z=MathMin(xabs,yabs);
//--- check
   if(z==0.0)
      result=w;
   else
      result=w*MathSqrt(1+CMath::Sqr(z/w));
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| Safe sqrt(x^2+y^2+z^2)                                           |
//+------------------------------------------------------------------+
double CApServ::SafePythag3(double x,double y,double z)
  {
   double w=MathMax(MathAbs(x),MathMax(MathAbs(y),MathAbs(z)));
//--- check
   if(w==0.0)
      return(0);
//--- change values
   x=x/w;
   y=y/w;
   z=z/w;
//--- return result
   return(w*MathSqrt(CMath::Sqr(x)+CMath::Sqr(y)+CMath::Sqr(z)));
  }
//+------------------------------------------------------------------+
//| Safe division.                                                   |
//| This function attempts to calculate R=X/Y without overflow.      |
//| It returns:                                                      |
//| * +1, if abs(X/Y)>=MaxRealNumber or undefined - overflow-like    |
//|       situation (no overlfow is generated, R is either NAN,      |
//|       PosINF, NegINF)                                            |
//| *  0, if MinRealNumber<abs(X/Y)<MaxRealNumber or X=0, Y<>0       |
//|       (R contains result, may be zero)                           |
//| * -1, if 0<abs(X/Y)<MinRealNumber - underflow-like situation     |
//|       (R contains zero; it corresponds to underflow)             |
//| No overflow is generated in any case.                            |
//+------------------------------------------------------------------+
int CApServ::SafeRDiv(double x,double y,double &r)
  {
//--- create variables
   int result=0;
//--- initialization
   r=0;
//--- Two special cases:
//--- * Y=0
//--- * X=0 and Y<>0
   if(y==0.0)
     {
      result=1;
      //--- check
      if(x==0.0)
         r=CInfOrNaN::NaN();
      //--- check
      if(x>0.0)
         r=CInfOrNaN::PositiveInfinity();
      //--- check
      if(x<0.0)
         r=CInfOrNaN::NegativeInfinity();
      //--- return result
      return(result);
     }
//--- check
   if(x==0.0)
     {
      r=0;
      result=0;
      //--- return result
      return(result);
     }
//--- make Y>0
   if(y<0.0)
     {
      x=-x;
      y=-y;
     }
//--- check
   if(y>=1.0)
     {
      r=x/y;
      //--- check
      if(MathAbs(r)<=CMath::m_minrealnumber)
        {
         result=-1;
         r=0;
        }
      else
         result=0;
     }
   else
     {
      //--- check
      if(MathAbs(x)>=CMath::m_maxrealnumber*y)
        {
         //--- check
         if(x>0.0)
            r=CInfOrNaN::PositiveInfinity();
         else
            r=CInfOrNaN::NegativeInfinity();
         result=1;
        }
      else
        {
         r=x/y;
         result=0;
        }
     }
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| This function calculates "safe" min(X/Y,V) for positive finite X,|
//| Y, V. No overflow is generated in any case.                      |
//+------------------------------------------------------------------+
double CApServ::SafeMinPosRV(const double x,const double y,const double v)
  {
//--- create variables
   double result=0;
   double r=0;
//--- check
   if(y>=1.0)
     {
      //--- Y>=1, we can safely divide by Y
      r=x/y;
      result=v;
      //--- check
      if(v>r)
         result=r;
      else
         result=v;
     }
   else
     {
      //--- Y<1, we can safely multiply by Y
      if(x<v*y)
         result=x/y;
      else
         result=v;
     }
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| This function makes periodic mapping of X to [A,B].              |
//| It accepts X, A, B (A>B). It returns T which lies in  [A,B] and  |
//| integer K, such that X = T + K*(B-A).                            |
//| NOTES:                                                           |
//| * K is represented as real value, although actually it is integer|
//| * T is guaranteed to be in [A,B]                                 |
//| * T replaces X                                                   |
//+------------------------------------------------------------------+
void CApServ::ApPeriodicMap(double &x,const double a,const double b,
                            double &k)
  {
//--- initialization
   k=0;
//--- check
   if(!CAp::Assert(a<b,__FUNCTION__+": internal error!"))
      return;
//--- initialization
   k=(int)MathFloor((x-a)/(b-a));
   x=x-k*(b-a);
//--- change values
   while(x<a)
     {
      x=x+(b-a);
      k=k-1;
     }
//--- change values
   while(x>b)
     {
      x=x-(b-a);
      k=k+1;
     }
//--- change values
   x=MathMax(x,a);
   x=MathMin(x,b);
  }
//+------------------------------------------------------------------+
//| Returns random normal number using low-quality system-provided   |
//| generator                                                        |
//+------------------------------------------------------------------+
double CApServ::RandomNormal(void)
  {
//--- create variables
   double result=0;
   double u=0;
   double v=0;
   double s=0;
//--- main loop
   while(true)
     {
      u=2*CMath::RandomReal()-1;
      v=2*CMath::RandomReal()-1;
      s=MathPow(u,2)+MathPow(v,2);
      if(s>0.0 && s<1.0)
        {
         //--- two Sqrt's instead of one to
         //--- avoid overflow when S is too small
         s=MathSqrt(-2*MathLog(s)/s);
         result=u*s;
         break;
        }
     }
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| Generates random unit vector using low-quality system-provided   |
//| generator.                                                       |
//| Reallocates array if its size is too short.                      |
//+------------------------------------------------------------------+
void CApServ::RandomUnit(int n,CRowDouble &x)
  {
//--- check
   if(!CAp::Assert(n>0,__FUNCTION__": N<=0"))
      return;
//--- create variables
   double v=0;
   double vv=0;

   if((int)x.Size()<n)
      x.Resize(n);

   do
     {
      v=0.0;
      for(int i=0; i<n; i++)
        {
         vv=RandomNormal();
         x.Set(i,vv);
         v=v+vv*vv;
        }
     }
   while(v<=0.0);
   v=1.0/MathSqrt(v);
   x*=v;
  }
//+------------------------------------------------------------------+
//| This function is used to swap two values                         |
//+------------------------------------------------------------------+
template <typename T>
void CApServ::Swap(T &v0,T &v1)
  {
   T v=v0;
   v0=v1;
   v1=v;
  }
//+------------------------------------------------------------------+
//| This function is used to swap two rows of the matrix; if NCols<0,|
//| automatically determined from the matrix size.                   |
//+------------------------------------------------------------------+
void CApServ::SwapRows(CMatrixDouble &a,int i0,int i1,int ncols)
  {
   if(i0!=i1)
      a.SwapRows((ulong)i0,(ulong)i1);
  }
//+------------------------------------------------------------------+
//| This function is used to swap two cols of the matrix; if NRows<0,|
//|  automatically determined from the matrix size.                  |
//+------------------------------------------------------------------+
void CApServ::SwapCols(CMatrixDouble &a,int j0,int j1,int nrows)
  {
   if(j0!=j1)
      a.SwapCols((ulong)j0,(ulong)j1);
  }


//+------------------------------------------------------------------+
//| This function is used to swap two "entries" in 1-dimensional     |
//| array composed from D-element entries                            |
//+------------------------------------------------------------------+
void CApServ::SwapEntries(CRowDouble &a,int i0,int i1,int entrywidth)
  {
//--- create variables
   int offs0=i0*entrywidth;
   int offs1=i1*entrywidth;
//--- quick exit
   if(i0==i1)
      return;

   for(int j=0; j<entrywidth; j++)
      a.Swap(offs0+j,offs1+j);
  }
//+------------------------------------------------------------------+
//| This function is used to swap two elements of the vector         |
//+------------------------------------------------------------------+
void CApServ::SwapElements(CRowDouble &a,int i0,int i1)
  {
   if(i0!=i1)
      a.Swap(i0,i1);
  }
//+------------------------------------------------------------------+
//| This function is used to swap two elements of the vector         |
//+------------------------------------------------------------------+
void CApServ::SwapElementsI(CRowInt &a,int i0,int i1)
  {
   if(i0!=i1)
      a.Swap(i0,i1);
  }
//+------------------------------------------------------------------+
//| This function performs two operations:                           |
//| 1. decrements value of integer variable, if it is positive       |
//| 2. explicitly sets variable to zero if it is non-positive        |
//| It is used by some algorithms to decrease value of internal      |
//| counters.                                                        |
//+------------------------------------------------------------------+
void CApServ::CountDown(int &v)
  {
   if(v>0)
      v--;
   else
      v=0;
  }
//+------------------------------------------------------------------+
//| This function returns +1 or -1 depending on sign of X.           |
//| x=0 results in +1 being returned.                                |
//+------------------------------------------------------------------+
double CApServ::PosSign(double x)
  {
   double result=0;

   if(x>=0.0)
      result=1.0;
   else
      result=-1.0;
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| This function returns (A div B) rounded up; it expects that A>0, |
//| B>0, but does not check it.                                      |
//+------------------------------------------------------------------+
int CApServ::IDivUp(int a,int b)
  {
   return((a+b-1)/b);
  }
//+------------------------------------------------------------------+
//| This function returns max(|r0|,|r1|,|r2|)                        |
//+------------------------------------------------------------------+
template <typename T>
T CApServ::RMaxAbs3(T r0,T r1,T r2)
  {
   T result=(T)MathMax(MathAbs(r0),MathAbs(r1));
   result=(T)MathMax(result,MathAbs(r2));
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| 'bounds' value: maps X to [B1,B2]                                |
//+------------------------------------------------------------------+
template <typename T>
T CApServ::BoundVal(const T x,const T b1,const T b2)
  {
//--- check
   if(x<=b1)
      return(b1);
//--- check
   if(x>=b2)
      return(b2);
//--- return result
   return(x);
  }
//+------------------------------------------------------------------+
//| Returns number of non-zeros                                      |
//+------------------------------------------------------------------+
int CApServ::CountNZ1(CRowDouble &v,int n)
  {
   int result=0;
//--- main loop
   for(int i=0; i<n; i++)
      if(v[i]!=0)
         result++;
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| Returns number of non-zeros                                      |
//+------------------------------------------------------------------+
int CApServ::CountNZ2(CMatrixDouble &v,int m,int n)
  {
   int result=0;
//--- main loop
   for(int i=0; i<m; i++)
      for(int j=0; j<n; j++)
         if(v.Get(i,j)!=0)
            result++;
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| Allocation of serializer: complex value                          |
//+------------------------------------------------------------------+
void CApServ::AllocComplex(CSerializer &s,complex &v)
  {
//--- entry
   s.Alloc_Entry();
   s.Alloc_Entry();
  }
//+------------------------------------------------------------------+
//| Serialization: complex value                                     |
//+------------------------------------------------------------------+
void CApServ::SerializeComplex(CSerializer &s,complex &v)
  {
//--- serialization
   s.Serialize_Double(v.real);
   s.Serialize_Double(v.imag);
  }
//+------------------------------------------------------------------+
//| Unserialization: complex value                                   |
//+------------------------------------------------------------------+
complex CApServ::UnserializeComplex(CSerializer &s)
  {
   complex result;
//--- unserialization
   result.real=s.Unserialize_Double();
   result.imag=s.Unserialize_Double();
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| Allocation of serializer: real array                             |
//+------------------------------------------------------------------+
void CApServ::AllocRealArray(CSerializer &s,double &v[],int n)
  {
//--- check
   if(n<0)
      n=CAp::Len(v);
//--- entry
   s.Alloc_Entry();
   for(int i=0; i<n; i++)
      s.Alloc_Entry();
  }
//+------------------------------------------------------------------+
//| Allocation of serializer: real array                             |
//+------------------------------------------------------------------+
void CApServ::AllocRealArray(CSerializer &s,CRowDouble &v,int n)
  {
//--- check
   if(n<0)
      n=(int)v.Size();
//--- entry
   s.Alloc_Entry();
   for(int i=0; i<n; i++)
      s.Alloc_Entry();
  }
//+------------------------------------------------------------------+
//| Serialization: real array                                        |
//+------------------------------------------------------------------+
void CApServ::SerializeRealArray(CSerializer &s,double &v[],int n)
  {
//--- check
   if(n<0)
      n=CAp::Len(v);
//--- serialization
   s.Serialize_Int(n);
   for(int i=0; i<n; i++)
      s.Serialize_Double(v[i]);
  }
//+------------------------------------------------------------------+
//| Serialization: real array                                        |
//+------------------------------------------------------------------+
void CApServ::SerializeRealArray(CSerializer &s,CRowDouble &v,int n)
  {
//--- check
   if(n<0)
      n=(int)v.Size();
//--- serialization
   s.Serialize_Int(n);
   for(int i=0; i<n; i++)
      s.Serialize_Double(v[i]);
  }
//+------------------------------------------------------------------+
//| Unserialization: real array                                      |
//+------------------------------------------------------------------+
void CApServ::UnserializeRealArray(CSerializer &s,double &v[])
  {
//--- unserialization
   int n=s.Unserialize_Int();
//--- check
   if(n==0)
      return;
//--- allocation
   ArrayResize(v,n);
//--- unserialization
   for(int i=0; i<n; i++)
      v[i]=s.Unserialize_Double();
  }
//+------------------------------------------------------------------+
//| Unserialization: real array                                      |
//+------------------------------------------------------------------+
void CApServ::UnserializeRealArray(CSerializer &s,CRowDouble &v)
  {
//--- unserialization
   int n=s.Unserialize_Int();
//--- check
   if(n==0)
      return;
//--- allocation
   v.Resize(n);
//--- unserialization
   for(int i=0; i<n; i++)
      v.Set(i,s.Unserialize_Double());
  }
//+------------------------------------------------------------------+
//| Allocation of serializer: Integer array                          |
//+------------------------------------------------------------------+
void CApServ::AllocIntegerArray(CSerializer &s,int &v[],int n)
  {
//--- check
   if(n<0)
      n=CAp::Len(v);
//--- entry
   s.Alloc_Entry();
   for(int i=0; i<n; i++)
      s.Alloc_Entry();
  }
//+------------------------------------------------------------------+
//| Allocation of serializer: Integer array                          |
//+------------------------------------------------------------------+
void CApServ::AllocIntegerArray(CSerializer &s,CRowInt &v,int n)
  {
//--- check
   if(n<0)
      n=v.Size();
//--- entry
   s.Alloc_Entry();
   for(int i=0; i<n; i++)
      s.Alloc_Entry();
  }
//+------------------------------------------------------------------+
//| Serialization: Integer array                                     |
//+------------------------------------------------------------------+
void CApServ::SerializeIntegerArray(CSerializer &s,int &v[],int n)
  {
//--- check
   if(n<0)
      n=CAp::Len(v);
//--- serialization
   s.Serialize_Int(n);
   for(int i=0; i<n; i++)
      s.Serialize_Int(v[i]);
  }
//+------------------------------------------------------------------+
//| Serialization: Integer array                                     |
//+------------------------------------------------------------------+
void CApServ::SerializeIntegerArray(CSerializer &s,CRowInt &v,int n)
  {
//--- check
   if(n<0)
      n=v.Size();
//--- serialization
   s.Serialize_Int(n);
   for(int i=0; i<n; i++)
      s.Serialize_Int(v[i]);
  }
//+------------------------------------------------------------------+
//| Unserialization: Integer array                                   |
//+------------------------------------------------------------------+
void CApServ::UnserializeIntegerArray(CSerializer &s,int &v[])
  {
//--- unserialization
   int n=s.Unserialize_Int();
//--- check
   if(n==0)
      return;
//--- allocation
   ArrayResizeAL(v,n);
   for(int i=0; i<n; i++)
      v[i]=s.Unserialize_Int();
  }
//+------------------------------------------------------------------+
//| Unserialization: Integer array                                   |
//+------------------------------------------------------------------+
void CApServ::UnserializeIntegerArray(CSerializer &s,CRowInt &v)
  {
//--- unserialization
   int n=s.Unserialize_Int();
//--- check
   if(n==0)
      return;
//--- allocation
   v.Resize(n);
   for(int i=0; i<n; i++)
      v.Set(i,s.Unserialize_Int());
  }
//+------------------------------------------------------------------+
//| Allocation of serializer: Bool array                             |
//+------------------------------------------------------------------+
void CApServ::AllocBoolArray(CSerializer &s,bool &v[],int n)
  {
//--- check
   if(n<0)
      n=(int)v.Size();
//--- entry
   s.Alloc_Entry();
   for(int i=0; i<n; i++)
      s.Alloc_Entry();
  }
//+------------------------------------------------------------------+
//| Serialization: Bool array                                        |
//+------------------------------------------------------------------+
void CApServ::SerializeBoolArray(CSerializer &s,bool &v[],int n)
  {
//--- check
   if(n<0)
      n=(int)v.Size();
//--- serialization
   s.Serialize_Int(n);
   for(int i=0; i<n; i++)
      s.Serialize_Bool(v[i]);
  }
//+------------------------------------------------------------------+
//| Allocation of serializer: real matrix                            |
//+------------------------------------------------------------------+
void CApServ::AllocRealMatrix(CSerializer &s,CMatrixDouble &v,int n0,int n1)
  {
//--- check
   if(n0<0)
      n0=(int)CAp::Rows(v);
//--- check
   if(n1<0)
      n1=(int)CAp::Cols(v);
//--- entry
   s.Alloc_Entry();
   s.Alloc_Entry();
   for(int i=0; i<n0; i++)
      for(int j=0; j<n1; j++)
         s.Alloc_Entry();
  }
//+------------------------------------------------------------------+
//| Serialization: real matrix                                       |
//+------------------------------------------------------------------+
void CApServ::SerializeRealMatrix(CSerializer &s,CMatrixDouble &v,int n0,int n1)
  {
//--- check
   if(n0<0)
      n0=(int)CAp::Rows(v);
//--- check
   if(n1<0)
      n1=(int)CAp::Cols(v);
//--- serialization
   s.Serialize_Int(n0);
//--- serialization
   s.Serialize_Int(n1);
   for(int i=0; i<n0; i++)
      for(int j=0; j<n1; j++)
         s.Serialize_Double(v.Get(i,j));
  }
//+------------------------------------------------------------------+
//| Unserialization: real matrix                                     |
//+------------------------------------------------------------------+
void CApServ::UnserializeRealMatrix(CSerializer &s,CMatrixDouble &v)
  {
//--- create variables
   int    i=0;
   int    j=0;
   int    n0=0;
   int    n1=0;
   double t=0;
//--- unserialization
   n0=s.Unserialize_Int();
   n1=s.Unserialize_Int();
//--- check
   if(n0==0 || n1==0)
      return;
//--- resize
   v.Resize(n0,n1);
//--- unserialization
   for(i=0; i<n0; i++)
      for(j=0; j<n1; j++)
         v.Set(i,j,s.Unserialize_Double());
  }
//+------------------------------------------------------------------+
//| Copy integer array                                               |
//+------------------------------------------------------------------+
void CApServ::CopyIntegerArray(int &src[],int &dst[])
  {
//--- check
   if(CAp::Len(src)>0)
     {
      //--- allocation
      ArrayResizeAL(dst,CAp::Len(src));
      //--- copy
      ArrayCopy(dst,src);
     }
  }
//+------------------------------------------------------------------+
//| Copy integer array                                               |
//+------------------------------------------------------------------+
void CApServ::CopyIntegerArray(CRowInt &src,CRowInt &dst)
  {
   dst=src;
  }
//+------------------------------------------------------------------+
//| Copy real array                                                  |
//+------------------------------------------------------------------+
void CApServ::CopyRealArray(double &src[],double &dst[])
  {
//--- check
   if(CAp::Len(src)>0)
     {
      ArrayResize(dst,CAp::Len(src));
      ArrayCopy(dst,src);
     }
  }
//+------------------------------------------------------------------+
//| Copy real array                                                  |
//+------------------------------------------------------------------+
void CApServ::CopyRealArray(double &src[],CRowDouble &dst)
  {
   dst=src;
  }
//+------------------------------------------------------------------+
//| Copy real array                                                  |
//+------------------------------------------------------------------+
void CApServ::CopyRealArray(CRowDouble &src,CRowDouble &dst)
  {
   dst=src;
  }
//+------------------------------------------------------------------+
//| Copy real matrix                                                 |
//+------------------------------------------------------------------+
void CApServ::CopyRealMatrix(CMatrixDouble &src,CMatrixDouble &dst)
  {
   dst=src;
  }
//+------------------------------------------------------------------+
//| This function is used in parallel functions for recurrent        |
//| division of large task into two smaller tasks.                   |
//| It has following properties:                                     |
//|   * it works only for TaskSize>=2 and TaskSize>TileSize          |
//|     (assertion is thrown otherwise)                              |
//|   * Task0+Task1=TaskSize, Task0>0, Task1>0                       |
//|   * Task0 and Task1 are close to each other                      |
//|   * Task0>=Task1                                                 |
//|   * Task0 is always divisible by TileSize                        |
//+------------------------------------------------------------------+
void CApServ::TiledSplit(int tasksize,int tilesize,
                         int &task0,int &task1)
  {
   task0=0;
   task1=0;
//---check
   if(!CAp::Assert(tasksize>=2,__FUNCTION__": TaskSize<2"))
      return;
   if(!CAp::Assert(tasksize>tilesize,__FUNCTION__": TaskSize<=TileSize"))
      return;
   int cc=ChunksCount(tasksize,tilesize);
   if(!CAp::Assert(cc>=2,__FUNCTION__": integrity check failed"))
      return;
   task0=IDivUp(cc,2)*tilesize;
   task1=tasksize-task0;
//--- check
   if(!CAp::Assert(task0>=1,__FUNCTION__": internal error"))
      return;
   if(!CAp::Assert(task1>=1,__FUNCTION__": internal error"))
      return;
   if(!CAp::Assert(task0%tilesize==0,__FUNCTION__": internal error"))
      return;
   if(!CAp::Assert(task0>=task1,__FUNCTION__": internal error"))
      return;
  }
//+------------------------------------------------------------------+
//| This function searches integer array. Elements in this array are |
//| actually records, each NRec elements wide. Each record has unique|
//| header - NHeader integer values, which identify it. Records are  |
//| lexicographically sorted by header.                              |
//| Records are identified by their index, not offset                |
//| (offset = NRec*index).                                           |
//| This function searches A (records with indices [I0,I1)) for a    |
//| record with header B. It returns index of this record            |
//| (not offset!), or -1 on failure.                                 |
//+------------------------------------------------------------------+
int CApServ::RecSearch(int &a[],const int nrec,const int nheader,int i0,int i1,int &b[])
  {
//--- create variables
   int mididx=0;
   int cflag=0;
   int k=0;
   int offs=0;
//--- cycle
   while(true)
     {
      //--- check
      if(i0>=i1)
         break;
      //--- change values
      mididx=(i0+i1)/2;
      offs=nrec*mididx;
      cflag=0;
      for(k=0; k<nheader ; k++)
        {
         //--- check
         if(a[offs+k]<b[k])
           {
            cflag=-1;
            break;
           }
         //--- check
         if(a[offs+k]>b[k])
           {
            cflag=1;
            break;
           }
        }
      //--- check
      if(cflag==0)
        {
         return(mididx);
        }
      //--- check
      if(cflag<0)
         i0=mididx+1;
      else
         i1=mididx;
     }
//--- return result
   return(-1);
  }
//+------------------------------------------------------------------+
//| Same                                                             |
//+------------------------------------------------------------------+
int CApServ::RecSearch(CRowInt &a,const int nrec,
                       const int nheader,int i0,
                       int i1,CRowInt &b)
  {
//--- create variables
   int mididx=0;
   int cflag=0;
   int k=0;
   int offs=0;
//--- cycle
   while(true)
     {
      //--- check
      if(i0>=i1)
         break;
      //--- change values
      mididx=(i0+i1)/2;
      offs=nrec*mididx;
      cflag=0;
      for(k=0; k<nheader; k++)
        {
         //--- check
         if(a[offs+k]<b[k])
           {
            cflag=-1;
            break;
           }
         //--- check
         if(a[offs+k]>b[k])
           {
            cflag=1;
            break;
           }
        }
      //--- check
      if(cflag==0)
         return(mididx);
      //--- check
      if(cflag<0)
         i0=mididx+1;
      else
         i1=mididx;
     }
//--- return result
   return(-1);
  }
//+------------------------------------------------------------------+
//| This function is used in parallel functions for recurrent        |
//| division of large task into two smaller tasks.                   |
//| It has following properties:                                     |
//|   * it works only for TaskSize>=2 (assertion is thrown otherwise)|
//|   * for TaskSize=2, it returns Task0=1, Task1=1                  |
//|   * in case TaskSize is odd,  Task0=TaskSize-1, Task1=1          |
//|   * in case TaskSize is even, Task0 and Task1 are approximately  |
//|     TaskSize/2 and both Task0 and Task1 are even, Task0>=Task1   |
//+------------------------------------------------------------------+
void CApServ::SplitLengthEven(int tasksize,int &task0,int &task1)
  {
//--- initialize
   task0=0;
   task1=0;
//---chek
   if(!CAp::Assert(tasksize>=2,__FUNCTION__": TaskSize<2"))
      return;
   if(tasksize==2)
     {
      task0=1;
      task1=1;
      return;
     }
   if(tasksize%2==0)
     {
      //--- Even division
      task1=task0=tasksize/2;
      if(task0%2!=0)
        {
         task0++;
         task1--;
        }
     }
   else
     {
      //--- Odd task size, split trailing odd part from it.
      task0=tasksize-1;
      task1=1;
     }
   if(!CAp::Assert(task0>=1,__FUNCTION__": internal error"))
      return;
   if(!CAp::Assert(task1>=1,__FUNCTION__": internal error"))
      return;
  }
//+------------------------------------------------------------------+
//| This function is used to calculate number of chunks (including   |
//| partial, non-complete chunks) in some set. It expects that       |
//| ChunkSize>=1, TaskSize>=0. Assertion is thrown otherwise.        |
//| Function result is equivalent to Ceil(TaskSize/ChunkSize), but   |
//| with guarantees that rounding errors won't ruin results.         |
//+------------------------------------------------------------------+
int CApServ::ChunksCount(int tasksize,int m_ChunkSize)
  {
   int result=0;
//--- check
   if(!CAp::Assert(tasksize>=0,__FUNCTION__": TaskSize<0"))
      return(-1);
   if(!CAp::Assert(m_ChunkSize>=1,__FUNCTION__": ChunkSize<1"))
      return(-1);

   result=(tasksize+m_ChunkSize-1)/m_ChunkSize;

   return(result);
  }
//+------------------------------------------------------------------+
//| The function performs zero-coalescing on real value.             |
//| NOTE: no check is performed for B<>0                             |
//+------------------------------------------------------------------+
double CApServ::Coalesce(double a,double b)
  {
   double result=a;
   if(a==0.0)
      result=b;

   return(result);
  }
//+------------------------------------------------------------------+
//| The function performs zero-coalescing on integer value.          |
//| NOTE: no check is performed for B<>0                             |
//+------------------------------------------------------------------+
int CApServ::CoalesceI(int a,int b)
  {
   int result=a;
   if(a==0)
      result=b;

   return(result);
  }
//+------------------------------------------------------------------+
//| The function calculates binary logarithm.                        |
//| NOTE: it costs twice as much as Ln(x)                            |
//+------------------------------------------------------------------+
double CApServ::LogBase2(double x)
  {
   return(MathLog(x)/MathLog(2.0));
  }
//+------------------------------------------------------------------+
//| This function compares two numbers for approximate equality, with|
//| tolerance to errors as large as tol.                             |
//+------------------------------------------------------------------+
bool CApServ::ApproxEqual(double a,double b,double tol)
  {
   return(MathAbs(a-b)<=tol);
  }
//+------------------------------------------------------------------+
//| This function compares two numbers for approximate equality, with|
//| tolerance to errors as large as max(|a|,|b|)*tol.                |
//+------------------------------------------------------------------+
bool CApServ::ApproxEqualRel(double a,double b,double tol)
  {
   return(MathAbs(a-b)<=MathMax(MathAbs(a),MathAbs(b))*tol);
  }
//+------------------------------------------------------------------+
//| Returns maximum density for level 2 sparse/dense functions.      |
//| Density values below one returned by this function are better to |
//| handle via sparse Level 2 functionality.                         |
//+------------------------------------------------------------------+
double CApServ::SparseLevel2Density(void)
  {
   return(0.1);
  }
//+------------------------------------------------------------------+
//| Returns A-tile size for a matrix.                                |
//| A-tiles are smallest tiles (32x32), suitable for processing by   |
//| ALGLIB own implementation of Level 3 linear algebra.             |
//+------------------------------------------------------------------+
int CApServ::MatrixTileSizeA(void)
  {
   return(32);
  }
//+------------------------------------------------------------------+
//| Returns B-tile size for a matrix.                                |
//| B-tiles are larger tiles (64x64), suitable for parallel execution|
//| or for processing by vendor's implementation of Level 3 linear   |
//| algebra.                                                         |
//+------------------------------------------------------------------+
int CApServ::MatrixTileSizeB(void)
  {
   return(64);
  }
//+------------------------------------------------------------------+
//| This function returns minimum cost of task which is feasible for |
//| multithreaded processing. It returns real number in order to     |
//| avoid overflow problems.                                         |
//+------------------------------------------------------------------+
double CApServ::SMPActivationLevel(void)
  {
   double nn=2*MatrixTileSizeB();
   return(MathMax(1.9*MathPow(nn,3),1.0E7));
  }
//+------------------------------------------------------------------+
//| This function returns minimum cost of task which is feasible for |
//| spawn (given that multithreading is active).                     |
//| It returns real number in order to avoid overflow problems.      |
//+------------------------------------------------------------------+
double CApServ::SpawnLevel(void)
  {
   double nn=2*MatrixTileSizeA();
   return(1.9*MathPow(nn,3));
  }
//+------------------------------------------------------------------+
//| --- OBSOLETE FUNCTION, USE TILED SPLIT INSTEAD ---               |
//| This function is used in parallel functions for recurrent        |
//| division of large task into two smaller tasks.                   |
//| It has following properties:                                     |
//|   * it works only for TaskSize>=2 and ChunkSize>=2 (assertion is |
//|     thrown otherwise)                                            |
//|   * Task0+Task1=TaskSize, Task0>0, Task1>0                       |
//|   * Task0 and Task1 are close to each other                      |
//|   * in case TaskSize>ChunkSize, Task0 is always divisible by     |
//|     ChunkSize                                                    |
//+------------------------------------------------------------------+
void CApServ::SplitLength(int tasksize,int m_ChunkSize,
                          int &task0,int &task1)
  {
   task0=0;
   task1=0;
//--- check
   if(!CAp::Assert(m_ChunkSize>=2,__FUNCTION__": ChunkSize<2"))
      return;
   if(!CAp::Assert(tasksize>=2,__FUNCTION__": TaskSize<2"))
      return;
   task0=tasksize/2;
   if(task0>m_ChunkSize && task0%m_ChunkSize!=0)
      task0=task0-task0%m_ChunkSize;
   task1=tasksize-task0;
//--- check
   if(!CAp::Assert(task0>=1,__FUNCTION__": internal error"))
      return;
   if(!CAp::Assert(task1>=1,__FUNCTION__": internal error"))
      return;
  }
//+------------------------------------------------------------------+
//| Outputs vector A[I0,I1-1] to trace log using either:             |
//|   a)  6-digit exponential format (no trace flags is set)         |
//|   b) 15-ditit exponential format ('PREC.E15' trace flag is set)  |
//|   c)  6-ditit fixed-point format ('PREC.F6' trace flag is set)   |
//| This function checks trace flags every time it is called.        |
//+------------------------------------------------------------------+
void CApServ::TraceVectorAutopRec(CRowDouble &a,int i0,int i1)
  {
   int prectouse=0;
//--- Determine precision to use
   if(CAp::IsTraceEnabled("PREC.E15"))
      prectouse=1;
   if(CAp::IsTraceEnabled("PREC.F6"))
      prectouse=2;
//--- Output
   CAp::Trace("[ ");
   for(int i=i0; i<i1; i++)
     {
      switch(prectouse)
        {
         case 0:
            CAp::Trace(StringFormat("%14.6E",a[i]));
            break;
         case 1:
            CAp::Trace(StringFormat("%23.15E",a[i]));
            break;
         case 2:
            CAp::Trace(StringFormat("%13.6F",a[i]));
            break;
        }
      if(i<i1-1)
         CAp::Trace(" ");
     }
   CAp::Trace(" ]");
  }
//+------------------------------------------------------------------+
//| Outputs row A[I,J0..J1-1] to trace log using either:             |
//|   a)  6-digit exponential format (no trace flags is set)         |
//|   b) 15-ditit exponential format ('PREC.E15' trace flag is set)  |
//|   c)  6-ditit fixed-point format ('PREC.F6' trace flag is set)   |
//| This function checks trace flags every time it is called.        |
//+------------------------------------------------------------------+
void CApServ::TraceRowAutopRec(CMatrixDouble &a,int i,int j0,int j1)
  {
//--- create variables
   CRowDouble Row=a.Row(i)+0;
   TraceVectorAutopRec(Row,j0,j1);
  }
//+------------------------------------------------------------------+
//| Unscales/unshifts vector A[N] by computing A*Scl+Sft and outputs |
//| result to trace log using either:                                |
//|   a)  6-digit exponential format (no trace flags is set)         |
//|   b) 15-ditit exponential format ('PREC.E15' trace flag is set)  |
//|   b)  6-ditit fixed-point format ('PREC.F6' trace flag is set)   |
//| This function checks trace flags every time it is called.        |
//| Both Scl and Sft can be omitted.                                 |
//+------------------------------------------------------------------+
void CApServ::TraceVectoRunScaledUnshiftedAutopRec(CRowDouble &x,
                                                   int n,
                                                   CRowDouble &scl,
                                                   bool applyscl,
                                                   CRowDouble &sft,
                                                   bool applysft)
  {
//--- create variables
   int    prectouse=0;
   double v=0;
//--- Determine precision to use
   if(CAp::IsTraceEnabled("PREC.E15"))
      prectouse=1;
   if(CAp::IsTraceEnabled("PREC.F6"))
      prectouse=2;
//--- Output
   CAp::Trace("[ ");
   for(int i=0; i<n; i++)
     {
      v=x[i];
      if(applyscl)
         v=v*scl[i];
      if(applysft)
         v=v+sft[i];
      switch(prectouse)
        {
         case 0:
            CAp::Trace(StringFormat("%14.6E",v));
            break;
         case 1:
            CAp::Trace(StringFormat("%23.15E",v));
            break;
         case 2:
            CAp::Trace(StringFormat("%13.6F",v));
            break;
        }
      if(i<n-1)
         CAp::Trace(" ");
     }
   CAp::Trace(" ]");
  }
//+------------------------------------------------------------------+
//| Unscales/unshifts vector A[N] by computing A*Scl+Sft and outputs |
//| result to trace log using either:                                |
//|   a)  6-digit exponential format (no trace flags is set)         |
//|   b) 15-ditit exponential format ('PREC.E15' trace flag is set)  |
//|   c)  6-ditit fixed-point format ('PREC.F6' trace flag is set)   |
//| This function checks trace flags every time it is called.        |
//| Both Scl and Sft can be omitted.                                 |
//+------------------------------------------------------------------+
void CApServ::TraceVectorUnscaledUnshiftedAutopRec(CRowDouble &x,
                                                   int n,
                                                   CRowDouble &scl,
                                                   bool applyscl,
                                                   CRowDouble &sft,
                                                   bool applysft)
  {
//--- create variables
   int    prectouse=0;
   double v=0;
//--- Determine precision to use
   if(CAp::IsTraceEnabled("PREC.E15"))
      prectouse=1;
   if(CAp::IsTraceEnabled("PREC.F6"))
      prectouse=2;
//--- Output
   CAp::Trace("[ ");
   for(int i=0; i<n; i++)
     {
      v=x[i];
      if(applyscl)
         v*=scl[i];
      if(applysft)
         v+=sft[i];
      switch(prectouse)
        {
         case 0:
            CAp::Trace(StringFormat("%14.6E",v));
            break;
         case 1:
            CAp::Trace(StringFormat("%23.15E",v));
            break;
         case 2:
            CAp::Trace(StringFormat("%13.6F",v));
            break;
        }
      if(i<n-1)
         CAp::Trace(" ");
     }
   CAp::Trace(" ]");
  }
//+------------------------------------------------------------------+
//| Outputs vector of 1-norms of rows [I0,I1-1] of                   |
//| A[I0...I1-1,J0...J1-1] to trace log using either:                |
//|   a) 6-digit exponential format (no trace flags is set)          |
//|   b) 15-ditit exponential format ('PREC.E15' trace flag is set)  |
//|   c) 6-ditit fixed-point format ('PREC.F6' trace flag is set)    |
//| This function checks trace flags every time it is called.        |
//+------------------------------------------------------------------+
void CApServ::TraceRowNrm1AutopRec(CMatrixDouble &a,int i0,int i1,
                                   int j0,int j1)
  {
//--- create variables
   double v=0;
   int    prectouse=0;
//--- Determine precision to use
   if(CAp::IsTraceEnabled("PREC.E15"))
      prectouse=1;
   if(CAp::IsTraceEnabled("PREC.F6"))
      prectouse=2;
//--- Output
   CAp::Trace("[ ");
   for(int i=i0; i<i1; i++)
     {
      v=0;
      for(int j=j0; j<j1; j++)
         v=MathMax(v,MathAbs(a.Get(i,j)));
      switch(prectouse)
        {
         case 0:
            CAp::Trace(StringFormat("%14.6E",v));
            break;
         case 1:
            CAp::Trace(StringFormat("%23.15E",v));
            break;
         case 2:
            CAp::Trace(StringFormat("%13.6F",v));
            break;
        }
      if(i<i1-1)
         CAp::Trace(" ");
     }
   CAp::Trace(" ]");
  }
//+------------------------------------------------------------------+
//| Outputs vector A[I0,I1-1] to trace log using E3 precision        |
//+------------------------------------------------------------------+
void CApServ::TraceVectorE3(CRowDouble &a,int i0,int i1)
  {
   CAp::Trace("[ ");
   for(int i=i0; i<i1; i++)
     {
      CAp::Trace(StringFormat("%11.3E}",a[i]));
      if(i<i1-1)
         CAp::Trace(" ");
     }
   CAp::Trace(" ]");
  }
//+------------------------------------------------------------------+
//| Outputs vector A[I0,I1-1] to trace log using E6 precision        |
//+------------------------------------------------------------------+
void CApServ::TraceVectorE6(CRowDouble &a,int i0,int i1)
  {
   CAp::Trace("[ ");
   for(int i=i0; i<i1; i++)
     {
      CAp::Trace(StringFormat("%14.6E",a[i]));
      if(i<i1-1)
         CAp::Trace(" ");
     }
   CAp::Trace(" ]");
  }
//+------------------------------------------------------------------+
//| Outputs vector A[I0,I1-1] to trace log using E8 or E15 precision |
//+------------------------------------------------------------------+
void CApServ::TraceVectorE615(CRowDouble &a,int i0,int i1,bool usee15)
  {
   if(!usee15)
     {
      TraceVectorE6(a,i0,i1);
      return;
     }

   CAp::Trace("[ ");
   for(int i=i0; i<i1; i++)
     {
      CAp::Trace(StringFormat("23.15E",a[i]));
      if(i<i1-1)
         CAp::Trace(" ");
     }
   CAp::Trace(" ]");
  }
//+------------------------------------------------------------------+
//| Outputs vector of 1-norms of rows [I0,I1-1] of                   |
//| A[I0...I1-1,J0...J1-1] to trace log using E8 precision           |
//+------------------------------------------------------------------+
void CApServ::TraceRowNrm1E6(CMatrixDouble &a,int i0,int i1,int j0,int j1)
  {
   CAp::Trace("[ ");
   for(int i=i0; i<i1; i++)
     {
      double v=0;
      for(int j=j0; j<j1; j++)
         v=MathMax(v,MathAbs(a.Get(i,j)));
      CAp::Trace(StringFormat("%14.6E",v));
      if(i<i1-1)
         CAp::Trace(" ");
     }
   CAp::Trace(" ]");
  }
//+------------------------------------------------------------------+
//| Tag Sort                                                         |
//+------------------------------------------------------------------+
class CTSort
  {
public:
   static void       TagSort(double &a[],const int n,int &p1[],int &p2[]);
   static void       TagSort(CRowDouble &a,const int n,CRowInt &p1,CRowInt &p2);
   static void       TagSortBuf(double &a[],const int n,int &p1[],int &p2[],CApBuff &buf);
   static void       TagSortBuf(CRowDouble &a,const int n,CRowInt &p1,CRowInt &p2,CApBuff &buf);
   static void       TagSortFastI(double &a[],int &b[],double &bufa[],int &bufb[],const int n);
   static void       TagSortFastI(double &a[],int &b[],CRowDouble &bufa,CRowInt &bufb,const int n);
   static void       TagSortFastI(CRowDouble &a,CRowInt &b,CRowDouble &bufa,CRowInt &bufb,const int n);
   static void       TagSortFastR(double &a[],double &b[],double &bufa[],double &bufb[],const int n);
   static void       TagSortFastR(CRowDouble &a,CRowDouble &b,CRowDouble &bufa,CRowDouble &bufb,const int n);
   static void       TagSortFast(double &a[],double &bufa[],const int n);
   static void       TagSortFast(CRowDouble &a,CRowDouble &bufa,const int n);
   static void       TagSortMiddleIR(CRowInt &a,CRowDouble &b,int offset,int n);
   static void       TagSortMiddleII(CRowInt &a,CRowInt &b,int offset,int n);
   static void       TagSortMiddleI(CRowInt &a,int offset,int n);
   static void       SortMiddleI(CRowInt &a,int offset,int n);
   static void       TagHeapPushI(double &a[],int &b[],int &n,const double va,const int vb);
   static void       TagHeapPushI(CRowDouble &a,CRowInt &b,int &n,const double va,const int vb);
   static void       TagHeapReplaceTopI(double &a[],int &b[],const int n,const double va,const int vb);
   static void       TagHeapReplaceTopI(CRowDouble &a,CRowInt &b,const int n,const double va,const int vb);
   static void       TagHeapPopI(double &a[],int &b[],int &n);
   static void       TagHeapPopI(CRowDouble &a,CRowInt &b,int &n);
   static int        LowerBound(CRowDouble &a,int n,double t);
   static int        UpperBound(CRowDouble &a,int n,double t);

private:
   static void       TagSortFastIRec(double &a[],int &b[],double &bufa[],int &bufb[],const int i1,const int i2);
   static void       TagSortFastIRec(double &a[],int &b[],CRowDouble &bufa,CRowInt &bufb,const int i1,const int i2);
   static void       TagSortFastIRec(CRowDouble &a,CRowInt &b,CRowDouble &bufa,CRowInt &bufb,const int i1,const int i2);
   static void       TagSortFastRRec(CRowDouble &a,CRowDouble &b,CRowDouble &bufa,CRowDouble &bufb,const int i1,const int i2);
   static void       TagSortFastRec(CRowDouble &a,CRowDouble &bufa,const int i1,const int i2);
  };
//+------------------------------------------------------------------+
//| This function sorts array of real keys by ascending.             |
//| Its results are:                                                 |
//| * sorted array A                                                 |
//| * permutation tables P1, P2                                      |
//| Algorithm outputs permutation tables using two formats:          |
//| * as usual permutation of [0..N-1]. If P1[i]=j, then sorted A[i] |
//|   contains value which was moved there from J-th position.       |
//| * as a sequence of pairwise permutations. Sorted A[] may be      |
//|    obtained byswaping A[i] and A[P2[i]] for all i from 0 to N-1. |
//| INPUT PARAMETERS:                                                |
//|     A       -   unsorted array                                   |
//|     N       -   array size                                       |
//| OUPUT PARAMETERS:                                                |
//|     A       -   sorted array                                     |
//|     P1, P2  -   permutation tables, array[N]                     |
//| NOTES:                                                           |
//|     this function assumes that A[] is finite; it doesn't checks  |
//|     that condition. All other conditions (size of input arrays,  |
//|     etc.) are not checked too.                                   |
//+------------------------------------------------------------------+
void CTSort::TagSort(double &a[],const int n,int &p1[],int &p2[])
  {
//--- create a variable
   CApBuff buf;
//--- function call
   TagSortBuf(a,n,p1,p2,buf);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CTSort::TagSort(CRowDouble &a,const int n,CRowInt &p1,CRowInt &p2)
  {
//--- create a variable
   CApBuff buf;
//--- function call
   TagSortBuf(a,n,p1,p2,buf);
  }
//+------------------------------------------------------------------+
//| Buffered variant of TagSort, which accepts preallocated output   |
//| arrays as well as special structure for buffered allocations. If |
//| arrays are too short, they are reallocated. If they are large    |
//| enough, no memoryallocation is done.                             |
//| It is intended to be used in the performance-critical parts of   |
//| code, where additional allocations can lead to severe performance|
//| degradation                                                      |
//+------------------------------------------------------------------+
void CTSort::TagSortBuf(double &a[],const int n,int &p1[],int &p2[],
                        CApBuff &buf)
  {
   CRowDouble A=a;
   CRowInt P1=p1;
   CRowInt P2=p1;
   TagSortBuf(A,n,P1,P2,buf);
   A.ToArray(a);
   P1.ToArray(p1);
   P2.ToArray(p2);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CTSort::TagSortBuf(CRowDouble &a,const int n,CRowInt &p1,CRowInt &p2,
                        CApBuff &buf)
  {
//--- create variables
   int i=0;
   int lv=0;
   int rv=0;
   int rp=0;
//--- Special cases
   if(n<=0)
      return;
//--- check
   if(n==1)
     {
      //--- function call
      CApServ::IVectorSetLengthAtLeast(p1,1);
      //--- function call
      CApServ::IVectorSetLengthAtLeast(p2,1);
      p1.Set(0,0);
      p2.Set(0,0);
      //--- exit the function
      return;
     }
//--- General case, N>1: prepare permutations table P1
   CApServ::IVectorSetLengthAtLeast(p1,n);
   for(i=0; i<n; i++)
      p1.Set(i,i);
//--- General case, N>1: sort, update P1
   CApServ::RVectorSetLengthAtLeast(buf.m_ra0,n);
//--- function call
   CApServ::IVectorSetLengthAtLeast(buf.m_ia0,n);
   TagSortFastI(a,p1,buf.m_ra0,buf.m_ia0,n);
//--- General case, N>1: fill permutations table P2
//--- To fill P2 we maintain two arrays:
//--- * PV (Buf.IA0), Position(Value). PV[i] contains position of I-th key at the moment
//--- * VP (Buf.IA1), Value(Position). VP[i] contains key which has position I at the moment
//--- At each step we making permutation of two items:
//--- Left,which is given by position/value pair LP/LV
//--- and Right,which is given by RP/RV
//--- and updating PV[] and VP[] correspondingly.
   CApServ::IVectorSetLengthAtLeast(buf.m_ia0,n);
//--- function call
   CApServ::IVectorSetLengthAtLeast(buf.m_ia1,n);
//--- function call
   CApServ::IVectorSetLengthAtLeast(p2,n);
   for(i=0; i<n; i++)
     {
      buf.m_ia0.Set(i,i);
      buf.m_ia1.Set(i,i);
     }
   for(i=0; i<n; i++)
     {
      //--- calculate LP, LV, RP, RV
      lv=buf.m_ia1[i];
      rv=p1[i];
      rp=buf.m_ia0[rv];
      //--- Fill P2
      p2.Set(i,rp);
      //--- update PV and VP
      buf.m_ia1.Set(i,rv);
      buf.m_ia1.Set(rp,lv);
      buf.m_ia0.Set(lv,rp);
      buf.m_ia0.Set(rv,i);
     }
  }
//+------------------------------------------------------------------+
//| Same as TagSort, but optimized for real keys and integer labels. |
//| A is sorted, and same permutations are applied to B.             |
//| NOTES:                                                           |
//| 1.  this function assumes that A[] is finite; it doesn't checks  |
//|     that condition. All other conditions (size of input arrays,  |
//|     etc.) are not checked too.                                   |
//| 2.  this function uses two buffers, BufA and BufB, each is N     |
//|     elements large. They may be preallocated (which will save    |
//|     some time) or not, in which case function will automatically |
//|     allocate memory.                                             |
//+------------------------------------------------------------------+
void CTSort::TagSortFastI(double &a[],int &b[],double &bufa[],
                          int &bufb[],const int n)
  {
//--- create variables
   int    i=0;
   int    j=0;
   bool   isascending;
   bool   isdescending;
   double tmpr=0;
   int    tmpi=0;
//--- Special case
   if(n<=1)
      return;
//--- Test for already sorted set
   isascending=true;
   isdescending=true;
   for(i=1; i<n; i++)
     {
      isascending=isascending && a[i]>=a[i-1];
      isdescending=isdescending && a[i]<=a[i-1];
     }
//--- check
   if(isascending)
      return;
//--- check
   if(isdescending)
     {
      for(i=0; i<=n-1; i++)
        {
         j=n-1-i;
         //--- check
         if(j<=i)
            break;
         //--- swap
         tmpr=a[i];
         a[i]=a[j];
         a[j]=tmpr;
         tmpi=b[i];
         b[i]=b[j];
         b[j]=tmpi;
        }
      //--- exit the function
      return;
     }
//--- General case
   if(CAp::Len(bufa)<n)
      ArrayResize(bufa,n);
//--- check
   if(CAp::Len(bufb)<n)
      ArrayResizeAL(bufb,n);
//--- function call
   TagSortFastIRec(a,b,bufa,bufb,0,n-1);
  }
//+------------------------------------------------------------------+
//| Same as TagSort, but optimized for real keys and integer labels. |
//| A is sorted, and same permutations are applied to B.             |
//| NOTES:                                                           |
//| 1.  this function assumes that A[] is finite; it doesn't checks  |
//|     that condition. All other conditions (size of input arrays,  |
//|     etc.) are not checked too.                                   |
//| 2.  this function uses two buffers, BufA and BufB, each is N     |
//|     elements large. They may be preallocated (which will save    |
//|     some time) or not, in which case function will automatically |
//|     allocate memory.                                             |
//+------------------------------------------------------------------+
void CTSort::TagSortFastI(double &a[],int &b[],CRowDouble &bufa,CRowInt &bufb,const int n)
  {
//--- create variables
   int    i=0;
   int    j=0;
   bool   isascending;
   bool   isdescending;
   double tmpr=0;
   int    tmpi=0;
//--- Special case
   if(n<=1)
      return;
//--- Test for already sorted set
   isascending=true;
   isdescending=true;
   for(i=1; i<n; i++)
     {
      isascending=isascending && a[i]>=a[i-1];
      isdescending=isdescending && a[i]<=a[i-1];
     }
//--- check
   if(isascending)
      return;
//--- check
   if(isdescending)
     {
      for(i=0; i<=n-1; i++)
        {
         j=n-1-i;
         //--- check
         if(j<=i)
            break;
         //--- swap
         tmpr=a[i];
         a[i]=a[j];
         a[j]=tmpr;
         tmpi=b[i];
         b[i]=b[j];
         b[j]=tmpi;
        }
      //--- exit the function
      return;
     }
//--- General case
   if((int)bufa.Size()<n)
      bufa.Resize(n);
//--- check
   if(bufb.Size()<n)
      bufb.Resize(n);
//--- function call
   TagSortFastIRec(a,b,bufa,bufb,0,n-1);
  }
//+------------------------------------------------------------------+
//| Same as TagSort, but optimized for real keys and integer labels. |
//| A is sorted, and same permutations are applied to B.             |
//| NOTES:                                                           |
//| 1.  this function assumes that A[] is finite; it doesn't checks  |
//|     that condition. All other conditions (size of input arrays,  |
//|     etc.) are not checked too.                                   |
//| 2.  this function uses two buffers, BufA and BufB, each is N     |
//|     elements large. They may be preallocated (which will save    |
//|     some time) or not, in which case function will automatically |
//|     allocate memory.                                             |
//+------------------------------------------------------------------+
void CTSort::TagSortFastI(CRowDouble &a,CRowInt &b,CRowDouble &bufa,CRowInt &bufb,const int n)
  {
//--- create variables
   int    i=0;
   int    j=0;
   bool   isascending;
   bool   isdescending;
   double tmpr=0;
   int    tmpi=0;
//--- Special case
   if(n<=1)
      return;
//--- Test for already sorted set
   isascending=true;
   isdescending=true;
   for(i=1; i<n; i++)
     {
      isascending=isascending && a[i]>=a[i-1];
      isdescending=isdescending && a[i]<=a[i-1];
     }
//--- check
   if(isascending)
      return;
//--- check
   if(isdescending)
     {
      for(i=0; i<=n-1; i++)
        {
         j=n-1-i;
         //--- check
         if(j<=i)
            break;
         //--- swap
         a.Swap(i,j);
         b.Swap(i,j);
        }
      //--- exit the function
      return;
     }
//--- General case
   if((int)bufa.Size()<n)
      bufa.Resize(n);
//--- check
   if((int)bufb.Size()<n)
      bufb.Resize(n);
//--- function call
   TagSortFastIRec(a,b,bufa,bufb,0,n-1);
  }
//+------------------------------------------------------------------+
//| Same as TagSort, but optimized for real keys and real labels.    |
//| A is sorted, and same permutations are applied to B.             |
//| NOTES:                                                           |
//| 1.  this function assumes that A[] is finite; it doesn't checks  |
//|     etc.) are not that condition. All other conditions (size of  |
//|     input arrays, checked too.                                   |
//| 2.  this function uses two buffers, BufA and BufB, each is N     |
//|     elements large. They may be preallocated (which will save    |
//|     some time) or not, in whichcase function will automatically  |
//|     allocate memory.                                             |
//+------------------------------------------------------------------+
void CTSort::TagSortFastR(double &a[],double &b[],double &bufa[],
                          double &bufb[],const int n)
  {
//--- create variables
   int    i=0;
   int    j=0;
   bool   isascending=true;
   bool   isdescending=true;
   double tmpr=0;
//--- Special case
   if(n<=1)
      return;
//--- Test for already sorted set
   for(i=1; i<=n-1; i++)
     {
      isascending=isascending && a[i]>=a[i-1];
      isdescending=isdescending && a[i]<=a[i-1];
     }
//--- check
   if(isascending)
      return;
//--- check
   if(isdescending)
     {
      for(i=0; i<=n-1; i++)
        {
         j=n-1-i;
         //--- check
         if(j<=i)
            break;
         //--- swap
         tmpr=a[i];
         a[i]=a[j];
         a[j]=tmpr;
         tmpr=b[i];
         b[i]=b[j];
         b[j]=tmpr;
        }
      //--- exit the function
      return;
     }
//--- General case
   if(CAp::Len(bufa)<n)
      ArrayResize(bufa,n);
//--- check
   if(CAp::Len(bufb)<n)
      ArrayResize(bufb,n);
//--- function call
   CRowDouble A=a;
   CRowDouble B=b;
   CRowDouble bufA=bufa;
   CRowDouble bufB=bufb;
   TagSortFastRRec(A,B,bufA,bufB,0,n-1);
   A.ToArray(a);
   B.ToArray(b);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CTSort::TagSortFastR(CRowDouble &a,CRowDouble &b,CRowDouble &bufa,
                          CRowDouble &bufb,const int n)
  {
//--- create variables
   int    i=0;
   int    j=0;
   bool   isascending=true;
   bool   isdescending=true;
   double tmpr=0;
//--- Special case
   if(n<=1)
      return;
//--- Test for already sorted set
   for(i=1; i<=n-1; i++)
     {
      isascending=isascending && a[i]>=a[i-1];
      isdescending=isdescending && a[i]<=a[i-1];
     }
//--- check
   if(isascending)
      return;
//--- check
   if(isdescending)
     {
      for(i=0; i<=n-1; i++)
        {
         j=n-1-i;
         //--- check
         if(j<=i)
            break;
         //--- swap
         a.Swap(i,j);
         b.Swap(i,j);
        }
      //--- exit the function
      return;
     }
//--- General case
   if(CAp::Len(bufa)<n)
      bufa.Resize(n);
//--- check
   if(CAp::Len(bufb)<n)
      bufb.Resize(n);
//--- function call
   TagSortFastRRec(a,b,bufa,bufb,0,n-1);
  }
//+------------------------------------------------------------------+
//| Same as TagSort, but optimized for real keys without labels.     |
//| A is sorted, and that's all.                                     |
//| NOTES:                                                           |
//| 1.  this function assumes that A[] is finite; it doesn't checks  |
//|     that condition. All other conditions (size of input arrays,  |
//|     etc.) are not checked too.                                   |
//| 2.  this function uses buffer, BufA, which is N elements large.  |
//|     It may be preallocated (which will save some time) or not,   |
//|     in which casefunction will automatically allocate memory.    |
//+------------------------------------------------------------------+
void CTSort::TagSortFast(double &a[],double &bufa[],const int n)
  {
//--- Special case
   if(n<=1)
      return;
//--- create variables
   int    i;
   int    j;
   bool   isAsCending=true;
   bool   isDesCending=true;
   double tmpr;
//--- Test for already sorted set
   for(i=1; i<n; i++)
     {
      isAsCending=isAsCending && a[i]>=a[i-1];
      isDesCending=isDesCending && a[i]<=a[i-1];
     }
//--- check
   if(isAsCending)
      return;
//--- check
   if(isDesCending)
     {
      for(i=0; i<n; i++)
        {
         j=n-1-i;
         if(j<=i)
            break;
         tmpr=a[i];
         a[i]=a[j];
         a[j]=tmpr;
        }
      //--- exit the function
      return;
     }
//--- General case
   if(CAp::Len(bufa)<n)
      ArrayResize(bufa,n);
//--- function call
   CRowDouble A=a;
   CRowDouble bufA=bufa;
   TagSortFastRec(A,bufA,0,n-1);
   A.ToArray(a);
   bufA.ToArray(bufa);
  }
//+------------------------------------------------------------------+
//| Same as TagSort, but optimized for real keys without labels.     |
//| A is sorted, and that's all.                                     |
//| NOTES:                                                           |
//| 1.  this function assumes that A[] is finite; it doesn't checks  |
//|     that condition. All other conditions (size of input arrays,  |
//|     etc.) are not checked too.                                   |
//| 2.  this function uses buffer, BufA, which is N elements large.  |
//|     It may be preallocated (which will save some time) or not,   |
//|     in which casefunction will automatically allocate memory.    |
//+------------------------------------------------------------------+
void CTSort::TagSortFast(CRowDouble &a,CRowDouble &bufa,const int n)
  {
//--- Special case
   if(n<=1)
      return;
//--- create variables
   int  i;
   int  j;
   bool isAsCending=true;
   bool isDesCending=true;
//--- Test for already sorted set
   for(i=1; i<n; i++)
     {
      isAsCending=isAsCending && a[i]>=a[i-1];
      isDesCending=isDesCending && a[i]<=a[i-1];
     }
//--- check
   if(isAsCending)
      return;
//--- check
   if(isDesCending)
     {
      for(i=0; i<n; i++)
        {
         j=n-1-i;
         if(j<=i)
            break;
         a.Swap(i,j);
        }
      //--- exit the function
      return;
     }
//--- General case
   if(CAp::Len(bufa)<n)
      bufa.Resize(n);
//--- function call
   TagSortFastRec(a,bufa,0,n-1);
  }
//+------------------------------------------------------------------+
//| Sorting function optimized for integer keys and real labels, can |
//| be used to sort middle of the array                              |
//| A is sorted, and same permutations are applied to B.             |
//| NOTES: this function assumes that A[] is finite; it doesn't      |
//|        checks that condition. All other conditions (size of input|
//|        arrays, etc.) are not checked too.                        |
//+------------------------------------------------------------------+
void CTSort::TagSortMiddleIR(CRowInt &a,CRowDouble &b,
                             int offset,int n)
  {
//--- Special cases
   if(n<=1)
      return;
//--- create variables
   int    i=0;
   int    k=0;
   int    t=0;
   int    tmp=0;
   double tmpr=0;
   int    p0=0;
   int    p1=0;
   int    at=0;
   int    ak=0;
   int    ak1=0;
   double bt=0;
//--- General case, N>1: sort, update B
   for(i=2; i<=n; i++)
     {
      t=i;
      while(t!=1)
        {
         k=t/2;
         p0=offset+k-1;
         p1=offset+t-1;
         ak=a[p0];
         at=a[p1];
         if(ak>=at)
            break;
         a.Swap(p0,p1);
         b.Swap(p0,p1);
         t=k;
        }
     }
   for(i=n-1; i>=1; i--)
     {
      p0=offset;
      p1=offset+i;
      at=a[p1];
      a.Swap(p1,p0);
      bt=b[p1];
      b.Swap(p1,p0);
      t=0;
      while(true)
        {
         k=2*t+1;
         if(k+1>i)
            break;
         p0=offset+t;
         p1=offset+k;
         ak=a[p1];
         if(k+1<i)
           {
            ak1=a[p1+1];
            if(ak1>ak)
              {
               ak=ak1;
               p1++;
               k++;
              }
           }
         if(at>=ak)
            break;
         a.Set(p1,at);
         a.Set(p0,ak);
         b.Set(p0,b[p1]);
         b.Set(p1,bt);
         t=k;
        }
     }
  }
//+------------------------------------------------------------------+
//| Sorting function optimized for integer keys and integer labels,  |
//| can be used to sort middle of the array                          |
//| A is sorted, and same permutations are applied to B.             |
//| NOTES: this function assumes that A[] is finite; it doesn't      |
//|        checks that condition. All other conditions (size of input|
//|        arrays, etc.) are not checked too.                        |
//+------------------------------------------------------------------+
void CTSort::TagSortMiddleII(CRowInt &a,CRowInt &b,int offset,int n)
  {
//--- Special cases
   if(n<=1)
      return;
//--- create variables
   int i=0;
   int k=0;
   int t=0;
   int tmp=0;
   int tmpi=0;
   int p0=0;
   int p1=0;
   int at=0;
   int ak=0;
   int ak1=0;
   int bt=0;
//--- General case, N>1: sort, update B
   for(i=2; i<=n; i++)
     {
      t=i;
      while(t!=1)
        {
         k=t/2;
         p0=offset+k-1;
         p1=offset+t-1;
         ak=a[p0];
         at=a[p1];
         if(ak>=at)
            break;
         a.Swap(p0,p1);
         b.Swap(p0,p1);
         t=k;
        }
     }
   for(i=n-1; i>=1; i--)
     {
      p0=offset+0;
      p1=offset+i;
      at=a[p1];
      a.Swap(p1,p0);
      bt=b[p1];
      b.Swap(p1,p0);
      t=0;
      while(true)
        {
         k=2*t+1;
         if(k+1>i)
            break;
         p0=offset+t;
         p1=offset+k;
         ak=a[p1];
         if(k+1<i)
           {
            ak1=a[p1+1];
            if(ak1>ak)
              {
               ak=ak1;
               p1=p1+1;
               k=k+1;
              }
           }
         if(at>=ak)
            break;
         a.Set(p1,at);
         a.Set(p0,ak);
         b.Set(p0,b[p1]);
         b.Set(p1,bt);
         t=k;
        }
     }
  }
//+------------------------------------------------------------------+
//| Sorting function optimized for integer keys and real labels, can |
//| be used to sort middle of the array                              |
//| A is sorted, and same permutations are applied to B.             |
//| NOTES: this function assumes that A[] is finite; it doesn't      |
//|        checks that condition. All other conditions (size of input|
//|        arrays, etc.) are not checked too.                        |
//+------------------------------------------------------------------+
void CTSort::TagSortMiddleI(CRowInt &a,int offset,int n)
  {
//--- Special cases
   if(n<=1)
      return;
//--- create variables
   int i=0;
   int k=0;
   int t=0;
   int tmp=0;
   int p0=0;
   int p1=0;
   int at=0;
   int ak=0;
   int ak1=0;
//--- General case, N>1: sort, update B
   for(i=2; i<=n; i++)
     {
      t=i;
      while(t!=1)
        {
         k=t/2;
         p0=offset+k-1;
         p1=offset+t-1;
         ak=a[p0];
         at=a[p1];
         if(ak>=at)
            break;
         a.Swap(p0,p1);
         t=k;
        }
     }
   for(i=n-1; i>=1; i--)
     {
      p0=offset+0;
      p1=offset+i;
      at=a[p1];
      a.Swap(p1,p0);
      t=0;
      while(true)
        {
         k=2*t+1;
         if(k+1>i)
            break;
         p0=offset+t;
         p1=offset+k;
         ak=a[p1];
         if(k+1<i)
           {
            ak1=a[p1+1];
            if(ak1>ak)
              {
               ak=ak1;
               p1=p1+1;
               k=k+1;
              }
           }
         if(at>=ak)
            break;
         a.Set(p1,at);
         a.Set(p0,ak);
         t=k;
        }
     }
  }
//+------------------------------------------------------------------+
//| Sorting function optimized for integer values (only keys, no     |
//| labels), can be used to sort middle of the array                 |
//+------------------------------------------------------------------+
void CTSort::SortMiddleI(CRowInt &a,int offset,int n)
  {
//--- Special cases
   if(n<=1)
      return;
//--- create variables
   int i=0;
   int k=0;
   int t=0;
   int tmp=0;
   int p0=0;
   int p1=0;
   int at=0;
   int ak=0;
   int ak1=0;
//--- General case, N>1: sort, update B
   for(i=2; i<=n; i++)
     {
      t=i;
      while(t!=1)
        {
         k=t/2;
         p0=offset+k-1;
         p1=offset+t-1;
         ak=a[p0];
         at=a[p1];
         if(ak>=at)
            break;
         a.Swap(p0,p1);
         t=k;
        }
     }
   for(i=n-1; i>=1; i--)
     {
      p0=offset+0;
      p1=offset+i;
      at=a[p1];
      a.Swap(p1,p0);
      t=0;
      while(true)
        {
         k=2*t+1;
         if(k+1>i)
            break;
         p0=offset+t;
         p1=offset+k;
         ak=a[p1];
         if(k+1<i)
           {
            ak1=a[p1+1];
            if(ak1>ak)
              {
               ak=ak1;
               p1=p1+1;
               k=k+1;
              }
           }
         if(at>=ak)
            break;
         a.Set(p1,at);
         a.Set(p0,ak);
         t=k;
        }
     }
  }
//+------------------------------------------------------------------+
//| Heap operations: adds element to the heap                        |
//| PARAMETERS:                                                      |
//|     A       -   heap itself, must be at least array[0..N]        |
//|     B       -   array of integer tags, which are updated         |
//|                 according to permutations in the heap            |
//|     N       -   size of the heap (without new element).          |
//|                 updated on output                                |
//|     VA      -   value of the element being added                 |
//|     VB      -   value of the tag                                 |
//+------------------------------------------------------------------+
void CTSort::TagHeapPushI(double &a[],int &b[],int &n,const double va,
                          const int vb)
  {
//--- check
   if(n<0)
      return;
//--- create variables
   int    j=0;
   int    k=0;
   double v=0;
//--- N=0 is a special case
   if(n==0)
     {
      a[0]=va;
      b[0]=vb;
      n=n+1;
      //--- exit the function
      return;
     }
//--- add current point to the heap
//--- (add to the bottom, then move up)
//--- we don't write point to the heap
//--- until its final position is determined
//--- (it allow us to reduce number of array access operations)
   j=n;
   n=n+1;
   while(j>0)
     {
      k=(j-1)/2;
      v=a[k];
      //--- check
      if(v<va)
        {
         //--- swap with higher element
         a[j]=v;
         b[j]=b[k];
         j=k;
        }
      else
        {
         //--- element in its place. terminate.
         break;
        }
     }
//--- change values
   a[j]=va;
   b[j]=vb;
  }
//+------------------------------------------------------------------+
//| Heap operations: adds element to the heap                        |
//| PARAMETERS:                                                      |
//|     A       -   heap itself, must be at least array[0..N]        |
//|     B       -   array of integer tags, which are updated         |
//|                 according to permutations in the heap            |
//|     N       -   size of the heap (without new element).          |
//|                 updated on output                                |
//|     VA      -   value of the element being added                 |
//|     VB      -   value of the tag                                 |
//+------------------------------------------------------------------+
void CTSort::TagHeapPushI(CRowDouble &a,CRowInt &b,int &n,const double va,
                          const int vb)
  {
//--- create variables
   int    j=0;
   int    k=0;
   double v=0;
//--- check
   if(n<0)
      return;
//--- N=0 is a special case
   if(n==0)
     {
      a.Set(0,va);
      b.Set(0,vb);
      n=n+1;
      //--- exit the function
      return;
     }
//--- add current point to the heap
//--- (add to the bottom, then move up)
//--- we don't write point to the heap
//--- until its final position is determined
//--- (it allow us to reduce number of array access operations)
   j=n;
   n=n+1;
   while(j>0)
     {
      k=(j-1)/2;
      v=a[k];
      //--- check
      if(v<va)
        {
         //--- swap with higher element
         a.Set(j,v);
         b.Set(j,b[k]);
         j=k;
        }
      else
        {
         //--- element in its place. terminate.
         break;
        }
     }
//--- change values
   a.Set(j,va);
   b.Set(j,vb);
  }
//+------------------------------------------------------------------+
//| Heap operations: replaces top element with new element           |
//| (which is moved down)                                            |
//| PARAMETERS:                                                      |
//|     A       -   heap itself, must be at least array[0..N-1]      |
//|     B       -   array of integer tags, which are updated         |
//|                 according to permutations in the heap            |
//|     N       -   size of the heap                                 |
//|     VA      -   value of the element which replaces top element  |
//|     VB      -   value of the tag                                 |
//+------------------------------------------------------------------+
void CTSort::TagHeapReplaceTopI(double &a[],int &b[],const int n,
                                const double va,const int vb)
  {
//--- create variables
   int    j=0;
   int    k1=0;
   int    k2=0;
   double v=0;
   double v1=0;
   double v2=0;
//--- check
   if(n<1)
      return;
//--- N=1 is a special case
   if(n==1)
     {
      a[0]=va;
      b[0]=vb;
      //--- exit the function
      return;
     }
//--- move down through heap:
//--- * J  -   current element
//--- * K1 -   first child (always exists)
//--- * K2 -   second child (may not exists)
//--- we don't write point to the heap
//--- until its final position is determined
//--- (it allow us to reduce number of array access operations)
   j=0;
   k1=1;
   k2=2;
   while(k1<n)
     {
      //--- check
      if(k2>=n)
        {
         //--- only one child.
         //--- swap and terminate (because this child
         //--- have no siblings due to heap structure)
         v=a[k1];
         //--- check
         if(v>va)
           {
            a[j]=v;
            b[j]=b[k1];
            j=k1;
           }
         break;
        }
      else
        {
         //--- two childs
         v1=a[k1];
         v2=a[k2];
         //--- check
         if(v1>v2)
           {
            //--- check
            if(va<v1)
              {
               a[j]=v1;
               b[j]=b[k1];
               j=k1;
              }
            else
               break;
           }
         else
           {
            //--- check
            if(va<v2)
              {
               a[j]=v2;
               b[j]=b[k2];
               j=k2;
              }
            else
               break;
           }
         //--- change values
         k1=2*j+1;
         k2=2*j+2;
        }
     }
//--- change values
   a[j]=va;
   b[j]=vb;
  }
//+------------------------------------------------------------------+
//| Heap operations: replaces top element with new element           |
//| (which is moved down)                                            |
//| PARAMETERS:                                                      |
//|     A       -   heap itself, must be at least array[0..N-1]      |
//|     B       -   array of integer tags, which are updated         |
//|                 according to permutations in the heap            |
//|     N       -   size of the heap                                 |
//|     VA      -   value of the element which replaces top element  |
//|     VB      -   value of the tag                                 |
//+------------------------------------------------------------------+
void CTSort::TagHeapReplaceTopI(CRowDouble &a,CRowInt &b,const int n,
                                const double va,const int vb)
  {
//--- create variables
   int    j=0;
   int    k1=0;
   int    k2=0;
   double v=0;
   double v1=0;
   double v2=0;
//--- check
   if(n<1)
      return;
//--- N=1 is a special case
   if(n==1)
     {
      a.Set(0,va);
      b.Set(0,vb);
      //--- exit the function
      return;
     }
//--- move down through heap:
//--- * J  -   current element
//--- * K1 -   first child (always exists)
//--- * K2 -   second child (may not exists)
//--- we don't write point to the heap
//--- until its final position is determined
//--- (it allow us to reduce number of array access operations)
   j=0;
   k1=1;
   k2=2;
   while(k1<n)
     {
      //--- check
      if(k2>=n)
        {
         //--- only one child.
         //--- swap and terminate (because this child
         //--- have no siblings due to heap structure)
         v=a[k1];
         //--- check
         if(v>va)
           {
            a.Set(j,v);
            b.Set(j,b[k1]);
            j=k1;
           }
         break;
        }
      else
        {
         //--- two childs
         v1=a[k1];
         v2=a[k2];
         //--- check
         if(v1>v2)
           {
            //--- check
            if(va<v1)
              {
               a.Set(j,v1);
               b.Set(j,b[k1]);
               j=k1;
              }
            else
               break;
           }
         else
           {
            //--- check
            if(va<v2)
              {
               a.Set(j,v2);
               b.Set(j,b[k2]);
               j=k2;
              }
            else
               break;
           }
         //--- change values
         k1=2*j+1;
         k2=2*j+2;
        }
     }
//--- change values
   a.Set(j,va);
   b.Set(j,vb);
  }
//+------------------------------------------------------------------+
//| Heap operations: pops top element from the heap                  |
//| PARAMETERS:                                                      |
//|     A       -   heap itself, must be at least array[0..N-1]      |
//|     B       -   array of integer tags, which are updated         |
//|                 according to permutations in the heap            |
//|     N       -   size of the heap, N>=1                           |
//| On output top element is moved to A[N-1], B[N-1], heap is        |
//| reordered, N is decreased by 1.                                  |
//+------------------------------------------------------------------+
void CTSort::TagHeapPopI(double &a[],int &b[],int &n)
  {
//--- create variables
   double va=0;
   int    vb=0;
//--- check
   if(n<1)
      return;
//--- N=1 is a special case
   if(n==1)
     {
      n=0;
      return;
     }
//--- swap top element and last element,
//--- then reorder heap
   va=a[n-1];
   vb=b[n-1];
   a[n-1]=a[0];
   b[n-1]=b[0];
   n=n-1;
//--- function call
   TagHeapReplaceTopI(a,b,n,va,vb);
  }
//+------------------------------------------------------------------+
//| Heap operations: pops top element from the heap                  |
//| PARAMETERS:                                                      |
//|     A       -   heap itself, must be at least array[0..N-1]      |
//|     B       -   array of integer tags, which are updated         |
//|                 according to permutations in the heap            |
//|     N       -   size of the heap, N>=1                           |
//| On output top element is moved to A[N-1], B[N-1], heap is        |
//| reordered, N is decreased by 1.                                  |
//+------------------------------------------------------------------+
void CTSort::TagHeapPopI(CRowDouble &a,CRowInt &b,int &n)
  {
//--- create variables
   double va=0;
   int    vb=0;
//--- check
   if(n<1)
      return;
//--- N=1 is a special case
   if(n==1)
     {
      n=0;
      return;
     }
//--- swap top element and last element,
//--- then reorder heap
   va=a[n-1];
   vb=b[n-1];
   a.Set(n-1,a[0]);
   b.Set(n-1,b[0]);
   n=n-1;
//--- function call
   TagHeapReplaceTopI(a,b,n,va,vb);
  }
//+------------------------------------------------------------------+
//| Search first element less than T in sorted array.                |
//| PARAMETERS:                                                      |
//|   A        -  sorted array by ascending from 0 to N-1            |
//|   N        -  number of elements in array                        |
//|   T        -  the desired element                                |
//| RESULT:                                                          |
//|   The very first element's index, which isn't less than T. In    |
//|   the case when there aren't such elements, returns N.           |
//+------------------------------------------------------------------+
int CTSort::LowerBound(CRowDouble &a,int n,double t)
  {
//--- create variables
   int l=n;
   int half=0;
   int first=0;
   int middle=0;

   while(l>0)
     {
      half=l/2;
      middle=first+half;
      if(a[middle]<t)
        {
         first=middle+1;
         l-=half+1;
        }
      else
         l=half;
     }
//--- return result
   return(first);
  }
//+------------------------------------------------------------------+
//| Search first element more than T in sorted array.                |
//| PARAMETERS:                                                      |
//|   A        -  sorted array by ascending from 0 to N-1            |
//|   N        -  number of elements in array                        |
//|   T        -  the desired element                                |
//| RESULT:                                                          |
//|   The very first element's index, which more than T. In the case |
//|   when there aren't such elements, returns N.                    |
//+------------------------------------------------------------------+
int CTSort::UpperBound(CRowDouble &a,int n,double t)
  {
//--- create variables
   int l=n;
   int half=0;
   int first=0;
   int middle=0;

   while(l>0)
     {
      half=l/2;
      middle=first+half;
      if(t<a[middle])
         l=half;
      else
        {
         first=middle+1;
         l-=half+1;
        }
     }
//--- return result
   return(first);
  }
//+------------------------------------------------------------------+
//| Internal TagSortFastI: sorts A[I1...I2] (both bounds are         |
//| included), applies same permutations to B.                       |
//+------------------------------------------------------------------+
void CTSort::TagSortFastIRec(double &a[],int &b[],double &bufa[],
                             int &bufb[],const int i1,const int i2)
  {
//--- create variables
   int    i=0;
   int    j=0;
   int    k=0;
   int    cntless=0;
   int    cnteq=0;
   int    cntgreater=0;
   double tmpr=0;
   int    tmpi=0;
   double v0=0;
   double v1=0;
   double v2=0;
   double vp=0;
//--- Fast exit
   if(i2<=i1)
      return;
//--- Non-recursive sort for small arrays
   if(i2-i1<=16)
     {
      for(j=i1+1; j<=i2; j++)
        {
         //--- Search elements [I1..J-1] for place to insert Jth element.
         //--- This code stops immediately if we can leave A[J] at J-th position
         //--- (all elements have same value of A[J] larger than any of them)
         tmpr=a[j];
         tmpi=j;
         for(k=j-1; k>=i1; k--)
           {
            //--- check
            if(a[k]<=tmpr)
               break;
            tmpi=k;
           }
         k=tmpi;
         //--- Insert Jth element into Kth position
         if(k!=j)
           {
            //--- change values
            tmpr=a[j];
            tmpi=b[j];
            for(i=j-1; i>=k; i--)
              {
               a[i+1]=a[i];
               b[i+1]=b[i];
              }
            a[k]=tmpr;
            b[k]=tmpi;
           }
        }
      //--- exit the function
      return;
     }
//--- Quicksort: choose pivot
//--- Here we assume that I2-I1>=2
   v0=a[i1];
   v1=a[i1+(i2-i1)/2];
   v2=a[i2];
//--- check
   if(v0>v1)
     {
      tmpr=v1;
      v1=v0;
      v0=tmpr;
     }
//--- check
   if(v1>v2)
     {
      tmpr=v2;
      v2=v1;
      v1=tmpr;
     }
//--- check
   if(v0>v1)
     {
      tmpr=v1;
      v1=v0;
      v0=tmpr;
     }
   vp=v1;
//--- now pass through A/B and:
//--- * move elements that are LESS than VP to the left of A/B
//--- * move elements that are EQUAL to VP to the right of BufA/BufB (in the reverse order)
//--- * move elements that are GREATER than VP to the left of BufA/BufB (in the normal order
//--- * move elements from the tail of BufA/BufB to the middle of A/B (restoring normal order)
//--- * move elements from the left of BufA/BufB to the end of A/B
   cntless=0;
   cnteq=0;
   cntgreater=0;
   for(i=i1; i<=i2; i++)
     {
      v0=a[i];
      //--- check
      if(v0<vp)
        {
         //--- LESS
         k=i1+cntless;
         //--- check
         if(i!=k)
           {
            a[k]=v0;
            b[k]=b[i];
           }
         cntless++;
         continue;
        }
      //--- check
      if(v0==vp)
        {
         //--- EQUAL
         k=i2-cnteq;
         bufa[k]=v0;
         bufb[k]=b[i];
         cnteq++;
         continue;
        }
      //--- GREATER
      k=i1+cntgreater;
      bufa[k]=v0;
      bufb[k]=b[i];
      cntgreater++;
     }
//--- change values
   for(i=0; i<=cnteq-1; i++)
     {
      j=i1+cntless+cnteq-1-i;
      k=i2+i-(cnteq-1);
      a[j]=bufa[k];
      b[j]=bufb[k];
     }
//--- change values
   for(i=0; i<=cntgreater-1; i++)
     {
      j=i1+cntless+cnteq+i;
      k=i1+i;
      a[j]=bufa[k];
      b[j]=bufb[k];
     }
//--- Sort left and right parts of the array (ignoring middle part)
   TagSortFastIRec(a,b,bufa,bufb,i1,i1+cntless-1);
//--- function call
   TagSortFastIRec(a,b,bufa,bufb,i1+cntless+cnteq,i2);
  }
//+------------------------------------------------------------------+
//| Internal TagSortFastI: sorts A[I1...I2] (both bounds are         |
//| included), applies same permutations to B.                       |
//+------------------------------------------------------------------+
void CTSort::TagSortFastIRec(double &a[],int &b[],CRowDouble &bufa,
                             CRowInt &bufb,const int i1,const int i2)
  {
//--- create variables
   int    i=0;
   int    j=0;
   int    k=0;
   int    cntless=0;
   int    cnteq=0;
   int    cntgreater=0;
   double tmpr=0;
   int    tmpi=0;
   double v0=0;
   double v1=0;
   double v2=0;
   double vp=0;
//--- Fast exit
   if(i2<=i1)
      return;
//--- Non-recursive sort for small arrays
   if(i2-i1<=16)
     {
      for(j=i1+1; j<=i2; j++)
        {
         //--- Search elements [I1..J-1] for place to insert Jth element.
         //--- This code stops immediately if we can leave A[J] at J-th position
         //--- (all elements have same value of A[J] larger than any of them)
         tmpr=a[j];
         tmpi=j;
         for(k=j-1; k>=i1; k--)
           {
            //--- check
            if(a[k]<=tmpr)
               break;
            tmpi=k;
           }
         k=tmpi;
         //--- Insert Jth element into Kth position
         if(k!=j)
           {
            //--- change values
            tmpr=a[j];
            tmpi=b[j];
            for(i=j-1; i>=k; i--)
              {
               a[i+1]=a[i];
               b[i+1]=b[i];
              }
            a[k]=tmpr;
            b[k]=tmpi;
           }
        }
      //--- exit the function
      return;
     }
//--- Quicksort: choose pivot
//--- Here we assume that I2-I1>=2
   v0=a[i1];
   v1=a[i1+(i2-i1)/2];
   v2=a[i2];
//--- check
   if(v0>v1)
     {
      tmpr=v1;
      v1=v0;
      v0=tmpr;
     }
//--- check
   if(v1>v2)
     {
      tmpr=v2;
      v2=v1;
      v1=tmpr;
     }
//--- check
   if(v0>v1)
     {
      tmpr=v1;
      v1=v0;
      v0=tmpr;
     }
   vp=v1;
//--- now pass through A/B and:
//--- * move elements that are LESS than VP to the left of A/B
//--- * move elements that are EQUAL to VP to the right of BufA/BufB (in the reverse order)
//--- * move elements that are GREATER than VP to the left of BufA/BufB (in the normal order
//--- * move elements from the tail of BufA/BufB to the middle of A/B (restoring normal order)
//--- * move elements from the left of BufA/BufB to the end of A/B
   cntless=0;
   cnteq=0;
   cntgreater=0;
   for(i=i1; i<=i2; i++)
     {
      v0=a[i];
      //--- check
      if(v0<vp)
        {
         //--- LESS
         k=i1+cntless;
         //--- check
         if(i!=k)
           {
            a[k]=v0;
            b[k]=b[i];
           }
         cntless=cntless+1;
         continue;
        }
      //--- check
      if(v0==vp)
        {
         //--- EQUAL
         k=i2-cnteq;
         bufa.Set(k,v0);
         bufb.Set(k,b[i]);
         cnteq=cnteq+1;
         continue;
        }
      //--- GREATER
      k=i1+cntgreater;
      bufa.Set(k,v0);
      bufb.Set(k,b[i]);
      cntgreater=cntgreater+1;
     }
//--- change values
   for(i=0; i<=cnteq-1; i++)
     {
      j=i1+cntless+cnteq-1-i;
      k=i2+i-(cnteq-1);
      a[j]=bufa[k];
      b[j]=bufb[k];
     }
//--- change values
   for(i=0; i<=cntgreater-1; i++)
     {
      j=i1+cntless+cnteq+i;
      k=i1+i;
      a[j]=bufa[k];
      b[j]=bufb[k];
     }
//--- Sort left and right parts of the array (ignoring middle part)
   TagSortFastIRec(a,b,bufa,bufb,i1,i1+cntless-1);
//--- function call
   TagSortFastIRec(a,b,bufa,bufb,i1+cntless+cnteq,i2);
  }
//+------------------------------------------------------------------+
//| Internal TagSortFastI: sorts A[I1...I2] (both bounds are         |
//| included), applies same permutations to B.                       |
//+------------------------------------------------------------------+
void CTSort::TagSortFastIRec(CRowDouble &a,CRowInt &b,CRowDouble &bufa,
                             CRowInt &bufb,const int i1,const int i2)
  {
//--- create variables
   int    i=0;
   int    j=0;
   int    k=0;
   int    cntless=0;
   int    cnteq=0;
   int    cntgreater=0;
   double tmpr=0;
   int    tmpi=0;
   double v0=0;
   double v1=0;
   double v2=0;
   double vp=0;
//--- Fast exit
   if(i2<=i1)
      return;
//--- Non-recursive sort for small arrays
   if(i2-i1<=16)
     {
      for(j=i1+1; j<=i2; j++)
        {
         //--- Search elements [I1..J-1] for place to insert Jth element.
         //--- This code stops immediately if we can leave A[J] at J-th position
         //--- (all elements have same value of A[J] larger than any of them)
         tmpr=a[j];
         tmpi=j;
         for(k=j-1; k>=i1; k--)
           {
            //--- check
            if(a[k]<=tmpr)
               break;
            tmpi=k;
           }
         k=tmpi;
         //--- Insert Jth element into Kth position
         if(k!=j)
           {
            //--- change values
            tmpr=a[j];
            tmpi=b[j];
            for(i=j-1; i>=k; i--)
              {
               a.Set(i+1,a[i]);
               b.Set(i+1,b[i]);
              }
            a.Set(k,tmpr);
            b.Set(k,tmpi);
           }
        }
      //--- exit the function
      return;
     }
//--- Quicksort: choose pivot
//--- Here we assume that I2-I1>=2
   v0=a[i1];
   v1=a[i1+(i2-i1)/2];
   v2=a[i2];
//--- check
   if(v0>v1)
     {
      tmpr=v1;
      v1=v0;
      v0=tmpr;
     }
//--- check
   if(v1>v2)
     {
      tmpr=v2;
      v2=v1;
      v1=tmpr;
     }
//--- check
   if(v0>v1)
     {
      tmpr=v1;
      v1=v0;
      v0=tmpr;
     }
   vp=v1;
//--- now pass through A/B and:
//--- * move elements that are LESS than VP to the left of A/B
//--- * move elements that are EQUAL to VP to the right of BufA/BufB (in the reverse order)
//--- * move elements that are GREATER than VP to the left of BufA/BufB (in the normal order
//--- * move elements from the tail of BufA/BufB to the middle of A/B (restoring normal order)
//--- * move elements from the left of BufA/BufB to the end of A/B
   cntless=0;
   cnteq=0;
   cntgreater=0;
   for(i=i1; i<=i2; i++)
     {
      v0=a[i];
      //--- check
      if(v0<vp)
        {
         //--- LESS
         k=i1+cntless;
         //--- check
         if(i!=k)
           {
            a.Set(k,v0);
            b.Set(k,b[i]);
           }
         cntless=cntless+1;
         continue;
        }
      //--- check
      if(v0==vp)
        {
         //--- EQUAL
         k=i2-cnteq;
         bufa.Set(k,v0);
         bufb.Set(k,b[i]);
         cnteq=cnteq+1;
         continue;
        }
      //--- GREATER
      k=i1+cntgreater;
      bufa.Set(k,v0);
      bufb.Set(k,b[i]);
      cntgreater=cntgreater+1;
     }
//--- change values
   for(i=0; i<=cnteq-1; i++)
     {
      j=i1+cntless+cnteq-1-i;
      k=i2+i-(cnteq-1);
      a.Set(j,bufa[k]);
      b.Set(j,bufb[k]);
     }
//--- change values
   for(i=0; i<=cntgreater-1; i++)
     {
      j=i1+cntless+cnteq+i;
      k=i1+i;
      a.Set(j,bufa[k]);
      b.Set(j,bufb[k]);
     }
//--- Sort left and right parts of the array (ignoring middle part)
   TagSortFastIRec(a,b,bufa,bufb,i1,i1+cntless-1);
//--- function call
   TagSortFastIRec(a,b,bufa,bufb,i1+cntless+cnteq,i2);
  }
//+------------------------------------------------------------------+
//| Internal TagSortFastR: sorts A[I1...I2] (both bounds are         |
//| included), applies same permutations to B.                       |
//+------------------------------------------------------------------+
void CTSort::TagSortFastRRec(CRowDouble &a,CRowDouble &b,CRowDouble &bufa,
                             CRowDouble &bufb,const int i1,const int i2)
  {
//--- create variables
   int    i=0;
   int    j=0;
   int    k=0;
   double tmpr=0;
   double tmpr2=0;
   int    tmpi=0;
   int    cntless=0;
   int    cnteq=0;
   int    cntgreater=0;
   double v0=0;
   double v1=0;
   double v2=0;
   double vp=0;
//--- Fast exit
   if(i2<=i1)
      return;
//--- Non-recursive sort for small arrays
   if(i2-i1<=16)
     {
      for(j=i1+1; j<=i2; j++)
        {
         //--- Search elements [I1..J-1] for place to insert Jth element.
         //--- This code stops immediatly if we can leave A[J] at J-th position
         //--- (all elements have same value of A[J] larger than any of them)
         tmpr=a[j];
         tmpi=j;
         for(k=j-1; k>=i1; k--)
           {
            //--- check
            if(a[k]<=tmpr)
               break;
            tmpi=k;
           }
         k=tmpi;
         //--- Insert Jth element into Kth position
         if(k!=j)
           {
            //--- change values
            tmpr=a[j];
            tmpr2=b[j];
            for(i=j-1; i>=k; i--)
              {
               a.Set(i+1,a[i]);
               b.Set(i+1,b[i]);
              }
            a.Set(k,tmpr);
            b.Set(k,tmpr2);
           }
        }
      //--- exit the function
      return;
     }
//--- Quicksort: choose pivot
//--- Here we assume that I2-I1>=16
   v0=a[i1];
   v1=a[i1+(i2-i1)/2];
   v2=a[i2];
//--- check
   if(v0>v1)
     {
      tmpr=v1;
      v1=v0;
      v0=tmpr;
     }
//--- check
   if(v1>v2)
     {
      tmpr=v2;
      v2=v1;
      v1=tmpr;
     }
//--- check
   if(v0>v1)
     {
      tmpr=v1;
      v1=v0;
      v0=tmpr;
     }
   vp=v1;
//--- now pass through A/B and:
//--- * move elements that are LESS than VP to the left of A/B
//--- * move elements that are EQUAL to VP to the right of BufA/BufB (in the reverse order)
//--- * move elements that are GREATER than VP to the left of BufA/BufB (in the normal order
//--- * move elements from the tail of BufA/BufB to the middle of A/B (restoring normal order)
//--- * move elements from the left of BufA/BufB to the end of A/B
   cntless=0;
   cnteq=0;
   cntgreater=0;
   for(i=i1; i<=i2; i++)
     {
      v0=a[i];
      //--- check
      if(v0<vp)
        {
         //--- LESS
         k=i1+cntless;
         //--- check
         if(i!=k)
           {
            a.Set(k,v0);
            b.Set(k,b[i]);
           }
         cntless++;
         continue;
        }
      //--- check
      if(v0==vp)
        {
         //--- EQUAL
         k=i2-cnteq;
         bufa.Set(k,v0);
         bufb.Set(k,b[i]);
         cnteq++;
         continue;
        }
      //--- GREATER
      k=i1+cntgreater;
      bufa.Set(k,v0);
      bufb.Set(k,b[i]);
      cntgreater++;
     }
//--- change values
   for(i=0; i<=cnteq-1; i++)
     {
      j=i1+cntless+cnteq-1-i;
      k=i2+i-(cnteq-1);
      a.Set(j,bufa[k]);
      b.Set(j,bufb[k]);
     }
//--- change values
   for(i=0; i<=cntgreater-1; i++)
     {
      j=i1+cntless+cnteq+i;
      k=i1+i;
      a.Set(j,bufa[k]);
      b.Set(j,bufb[k]);
     }
//--- Sort left and right parts of the array (ignoring middle part)
   TagSortFastRRec(a,b,bufa,bufb,i1,i1+cntless-1);
//--- function call
   TagSortFastRRec(a,b,bufa,bufb,i1+cntless+cnteq,i2);
  }
//+------------------------------------------------------------------+
//| Internal TagSortFastI: sorts A[I1...I2] (both bounds are         |
//| included), applies same permutations to B.                       |
//+------------------------------------------------------------------+
void CTSort::TagSortFastRec(CRowDouble &a,CRowDouble &bufa,
                            const int i1,const int i2)
  {
//--- check
   if(i2<=i1)
      return;
//--- create variables
   int    cntLess=0;
   int    cntEq=0;
   int    cntGreat=0;
   int    i=0;
   int    j=0;
   int    k=0;
   double tmpr=0;
   int    tmpi=0;
   double v0=0;
   double v1=0;
   double v2=0;
   double vp=0;
//--- Non-recursive sort for small arrays
   if(i2-i1<=16)
     {
      for(j=i1+1; j<=i2; j++)
        {
         //--- Search elements [I1..J-1] for place to insert Jth element.
         //--- This code stops immediatly if we can leave A[J] at J-th position
         //--- (all elements have same value of A[J] larger than any of them)
         tmpr=a[j];
         tmpi=j;
         for(k=j-1; k>=i1; k--)
           {
            //--- check
            if(a[k]<=tmpr)
               break;
            tmpi=k;
           }
         k=tmpi;
         //--- Insert Jth element into Kth position
         if(k!=j)
           {
            tmpr=a[j];
            for(i=j-1; i>=k; i--)
               a.Set(i+1,a[i]);
            a.Set(k,tmpr);
           }
        }
      return;
     }
//--- Quicksort: choose pivot
//--- Here we assume that I2-I1>=16
   v0=a[i1];
   v1=a[i1+(i2-i1)/2];
   v2=a[i2];
//--- check
   if(v0>v1)
     {
      tmpr=v1;
      v1=v0;
      v0=tmpr;
     }
//--- check
   if(v1>v2)
     {
      tmpr=v2;
      v2=v1;
      v1=tmpr;
     }
//--- check
   if(v0>v1)
     {
      tmpr=v1;
      v1=v0;
      v0=tmpr;
     }
   vp=v1;
//--- now pass through A/B and:
//--- * move elements that are LESS than VP to the left of A/B
//--- * move elements that are EQUAL to VP to the right of BufA/BufB (in the reverse order)
//--- * move elements that are GREATER than VP to the left of BufA/BufB (in the normal order
//--- * move elements from the tail of BufA/BufB to the middle of A/B (restoring normal order)
//--- * move elements from the left of BufA/BufB to the end of A/B
   for(i=i1; i<=i2; i++)
     {
      v0=a[i];
      //--- check
      if(v0<vp)
        {
         //--- LESS
         k=i1+cntLess;
         if(i!=k)
            a.Set(k,v0);
         cntLess++;
         continue;
        }
      //--- check
      if(v0==vp)
        {
         //--- EQUAL
         k=i2-cntEq;
         bufa.Set(k,v0);
         cntEq++;
         continue;
        }
      //--- GREATER
      k=i1+cntGreat;
      bufa.Set(k,v0);
      cntGreat++;
     }
//--- change values
   for(i=0; i<cntEq; i++)
     {
      j=i1+cntLess+cntEq-1-i;
      k=i2+i-(cntEq-1);
      a.Set(j,bufa[k]);
     }
   for(i=0; i<cntGreat; i++)
     {
      j=i1+cntLess+cntEq+i;
      k=i1+i;
      a.Set(j,bufa[k]);
     }
//--- Sort left and right parts of the array (ignoring middle part)
   TagSortFastRec(a,bufa,i1,i1+cntLess-1);
   TagSortFastRec(a,bufa,i1+cntLess+cntEq,i2);
  }
//+------------------------------------------------------------------+
//| Calculation of basic statistical properties                      |
//+------------------------------------------------------------------+
class CBasicStatOps
  {
public:
   static void       RankX(CRowDouble &x,const int n,bool IsCentered,CApBuff &buf);
   static void       RankXUntied(CRowDouble &x,int n,CApBuff &buf);
  };
//+------------------------------------------------------------------+
//| Internal tied ranking subroutine.                                |
//| INPUT PARAMETERS:                                                |
//|   X        -  array to rank                                      |
//|   N        -  array size                                         |
//|   IsCentered- whether ranks are centered or not:                 |
//|               * True - ranks are centered in such way that their |
//|                        sum is zero                               |
//|               * False - ranks are not centered                   |
//|   Buf      -  temporary buffers                                  |
//| NOTE: when IsCentered is True and all X[] are equal, this        |
//|       function fills X by zeros (exact zeros are used, not sum   |
//|       which is only approximately equal to zero).                |
//+------------------------------------------------------------------+
void CBasicStatOps::RankX(CRowDouble &x,const int n,
                          bool IsCentered,CApBuff &buf)
  {
//--- Prepare
   if(n<1)
      return;
//--- check
   if(n==1)
     {
      x.Set(0,1);
      return;
     }
//--- create variables
   int    i;
   int    j;
   int    k;
   double tmp=0;
   double voffs=0;
//--- check
   if((int)buf.m_ra1.Size()<n)
      buf.m_ra1.Resize(n);
//--- check
   if((int)buf.m_ia1.Size()<n)
      buf.m_ia1.Resize(n);
//--- copy
   for(i=0; i<n; i++)
     {
      buf.m_ra1.Set(i,x[i]);
      buf.m_ia1.Set(i,i);
     }
   CTSort::TagSortFastI(buf.m_ra1,buf.m_ia1,buf.m_ra2,buf.m_ia2,n);
//--- Special test for all values being equal
   if(buf.m_ra1[0]==buf.m_ra1[n-1])
     {
      if(IsCentered)
         tmp=0.0;
      else
         tmp=(double)(n-1)/(2.0);
      for(i=0; i<n; i++)
         x.Set(i,tmp);
      return;
     }
//--- compute tied ranks
   i=0;
   while(i<n)
     {
      for(j=i+1; j<n; j++)
         if(buf.m_ra1[j]!=buf.m_ra1[i])
            break;
      for(k=i; k<j; k++)
         buf.m_ra1.Set(k,(double)(i+j-1)/2.0);
      i=j;
     }
//--- back to x
   if(IsCentered)
      voffs=(double)(n-1)/2.0;
   else
      voffs=0.0;
   for(i=0; i<n; i++)
      x.Set(buf.m_ia1[i],buf.m_ra1[i]-voffs);
  }
//+------------------------------------------------------------------+
//| Internal untied ranking subroutine.                              |
//| INPUT PARAMETERS:                                                |
//|   X        -  array to rank                                      |
//|   N        -  array size                                         |
//|   Buf      -  temporary buffers                                  |
//| Returns untied ranks (in case of a tie ranks are resolved        |
//| arbitrarily).                                                    |
//+------------------------------------------------------------------+
void CBasicStatOps::RankXUntied(CRowDouble &x,int n,CApBuff &buf)
  {
//--- Prepare
   if(n<1)
      return;

   if(n==1)
     {
      x.Set(0,0);
      return;
     }
   if(CAp::Len(buf.m_ra1)<n)
      buf.m_ra1.Resize(n);
   if(CAp::Len(buf.m_ia1)<n)
      buf.m_ia1.Resize(n);
   for(int i=0; i<n; i++)
     {
      buf.m_ra1.Set(i,x[i]);
      buf.m_ia1.Set(i,i);
     }
   CTSort::TagSortFastI(buf.m_ra1,buf.m_ia1,buf.m_ra2,buf.m_ia2,n);
   for(int i=0; i<n; i++)
      x.Set(buf.m_ia1[i],i);
  }
//+------------------------------------------------------------------+
//| Class includes templates for future functions                    |
//+------------------------------------------------------------------+
class CAblasF
  {
public:
   static void       RAddVC(int n,double alpha,CRowDouble &y,CMatrixDouble &x,int colidx);
   static void       RSetAllocV(int n,double v,CRowDouble &x);
   static void       RSetAllocM(int m,int n,double v,CMatrixDouble &a);
   static void       RAllocV(int n,CRowDouble &x);
   static void       IAllocV(int n,CRowInt &x);
   static void       BAllocV(int n,bool &x[]);
   static void       RAllocM(int m,int n,CMatrixDouble &a);
   static void       ISetAllocV(int n,int v,CRowInt &x);
   static void       BSetAllocV(int n,bool v,bool &x[]);
   static void       RSetC(int n,double v,CMatrixDouble &a,int j);
   static void       RCopyAllocV(int n,CRowDouble &x,CRowDouble &y);
   static void       RCopyM(int m,int n,CMatrixDouble &x,CMatrixDouble &y);
   static void       RCopyAllocM(int m,int n,CMatrixDouble &x,CMatrixDouble &y);
   static void       ICopyAllocV(int n,CRowInt &x,CRowInt &y);
   static void       BCopyAllocV(int n,bool &x[],bool &y[]);
   static void       IGrowV(int newn,CRowInt &x);
   static void       RGrowV(int newn,CRowDouble &x);
   static void       RCopyMulVC(int n,double v,CRowDouble &x,CMatrixDouble &y,int cidx);
   static void       RCopyVC(int n,CRowDouble &x,CMatrixDouble &a,int j);
   static void       RCopyCV(int n,CMatrixDouble &a,int j,CRowDouble &x);
   static void       CMatrixGemmK(int m,int n,int k,complex alpha,const CMatrixComplex &a,int ia,int ja,int optypea,const CMatrixComplex &b,int ib,int jb,int optypeb,complex beta,CMatrixComplex &c,int ic,int jc);
   static void       RMatrixGemmK(int m,int n,int k,double alpha,const CMatrixDouble &a,int ia,int ja,int optypea,const CMatrixDouble &b,int ib,int jb,int optypeb,double beta,CMatrixDouble &c,int ic,int jc);
   static void       RMatrixGemmK44v00(int m,int n,int k,double alpha,const CMatrixDouble &a,int ia,int ja,const CMatrixDouble &b,int ib,int jb,double beta,CMatrixDouble &c,int ic,int jc);
   static void       RMatrixGemmK44v01(int m,int n,int k,double alpha,const CMatrixDouble &a,int ia,int ja,const CMatrixDouble &b,int ib,int jb,double beta,CMatrixDouble &c,int ic,int jc);
   static void       RMatrixGemmK44v10(int m,int n,int k,double alpha,const CMatrixDouble &a,int ia,int ja,const CMatrixDouble &b,int ib,int jb,double beta,CMatrixDouble &c,int ic,int jc);
   static void       RMatrixGemmK44v11(int m,int n,int k,double alpha,const CMatrixDouble &a,int ia,int ja,const CMatrixDouble &b,int ib,int jb,double beta,CMatrixDouble &c,int ic,int jc);
   //---
   static double     RDotV(int n,CRowDouble &x,CRowDouble &y);
   static double     RDotVR(int n,CRowDouble &x,CMatrixDouble &a,int i);
   static double     RDotVC(int n,CRowDouble &x,CMatrixDouble &a,int i);
   static double     RDotRR(int n,CMatrixDouble &a,int ia,CMatrixDouble &b,int ib);
   static double     RDotV2(int n,CRowDouble &x);
   static void       RAddV(int n,double alpha,CRowDouble &y,CRowDouble &x);
   static void       RAddVX(int n,double alpha,CRowDouble &y,int offsy,CRowDouble &x,int offsx);
   static void       RAddVR(int n,double alpha,CRowDouble &y,CMatrixDouble &x,int rowidx);
   static void       RAddRV(int n,double alpha,CMatrixDouble &y,int ridx,CRowDouble &x);
   static void       RAddRR(int n,double alpha,CMatrixDouble &y,int ridxsrc,CMatrixDouble &x,int ridxdst);
   static void       RMulAddV(int n,CRowDouble &y,CRowDouble &z,CRowDouble &x);
   static void       RMulV(int n,double v,CRowDouble &x);
   static void       RMulR(int n,double v,CMatrixDouble &x,int rowidx);
   static void       RMulVX(int n,double v,CRowDouble &x,int offsx);
   static void       RNegMulAddV(int n,CRowDouble &y,CRowDouble &z,CRowDouble &x);
   static void       RMergeMulV(int n,CRowDouble &y,CRowDouble &x);
   static void       RMergeMulVR(int n,CRowDouble &y,CMatrixDouble &x,int rowidx);
   static void       RMergeMulRV(int n,CMatrixDouble &y,int rowidy,CRowDouble &x);
   static void       RMergeDivV(int n,CRowDouble &y,CRowDouble &x);
   static void       RMergeDivVR(int n,CRowDouble &y,CMatrixDouble &x,int rowidx);
   static void       RMergeDivRV(int n,CMatrixDouble &y,int rowidy,CRowDouble &x);
   static void       RMergeMaxV(int n,CRowDouble &y,CRowDouble &x);
   static void       RMergeMaxVR(int n,CRowDouble &y,CMatrixDouble &x,int rowidx);
   static void       RMergeMaxRV(int n,CMatrixDouble &x,int rowidx,CRowDouble &y);
   static void       RMergeMinV(int n,CRowDouble &y,CRowDouble &x);
   static void       RMergeMinVR(int n,CRowDouble &y,CMatrixDouble &x,int rowidx);
   static void       RMergeMinRV(int n,CMatrixDouble &x,int rowidx,CRowDouble &y);
   static void       RSqrtV(int n,CRowDouble &x);
   static void       RSqrtR(int n,CMatrixDouble &x,int rowidx);
   static double     RMaxV(int n,CRowDouble &x);
   static double     RMaxAbsV(int n,CRowDouble &x);
   static double     RMaxR(int n,CMatrixDouble &x,int rowidx);
   static double     RMaxAbsR(int n,CMatrixDouble &x,int rowidx);
   static void       RSetV(int n,double v,CRowDouble &x);
   static void       RSetVX(int n,double v,CRowDouble &x,int offsx);
   static void       ISetV(int n,int v,CRowInt &x);
   static void       BSetV(int n,bool v,bool &x[]);
   static void       RSetM(int m,int n,double v,CMatrixDouble &a);
   static void       RSetR(int n,double v,CMatrixDouble &a,int i);
   //--- copy
   static void       BCopyV(int n,bool &x[],bool &y[]);
   static void       RCopyV(int n,CRowDouble &x,CRowDouble &y);
   static void       RCopyVX(int n,CRowDouble &x,int offsx,CRowDouble &y,int offsy);
   static void       RCopyVR(int n,CRowDouble &x,CMatrixDouble &a,int i);
   static void       RCopyRV(int n,CMatrixDouble &a,int i,CRowDouble &x);
   static void       RCopyRR(int n,CMatrixDouble &a,int i,CMatrixDouble &b,int k);
   static void       RCopyMulV(int n,double v,CRowDouble &x,CRowDouble &y);
   static void       RCopyMulVR(int n,double v,CRowDouble &x,CMatrixDouble &y,int ridx);
   static void       RCopyMulAddV(int n,CRowDouble &y,CRowDouble &z,CRowDouble &x,CRowDouble &r);
   static void       RCopyNegMulAddV(int n,CRowDouble &y,CRowDouble &z,CRowDouble &x,CRowDouble &r);
   static void       ICopyV(int n,CRowInt &x,CRowInt &y);
   static void       ICopyVX(int n,CRowInt &x,int offsx,CRowInt &y,int offsy);
   //---
   static void       RGemV(int m,int n,double alpha,CMatrixDouble &a,int opa,CRowDouble &x,double beta,CRowDouble &y);
   static void       RGemVX(int m,int n,double alpha,CMatrixDouble &a,int ia,int ja,int opa,CRowDouble &x,int ix,double beta,CRowDouble &y,int iy);
   static void       RGer(int m,int n,double alpha,CRowDouble &u,CRowDouble &v,CMatrixDouble &a);
   static void       RTrsVX(int n,CMatrixDouble &a,int ia,int ja,bool IsUpper,bool IsUnit,int OpType,CRowDouble &x,int ix);
  };
//+------------------------------------------------------------------+
//| Performs inplace addition of vector Y[] to column X[]            |
//| INPUT PARAMETERS:                                                |
//|   N     -  vector length                                         |
//|   Alpha -  multiplier                                            |
//|   Y     -  vector to add                                         |
//|   X     -  target column ColIdx                                  |
//| RESULT:                                                          |
//|   X := X + alpha*Y                                               |
//+------------------------------------------------------------------+
void CAblasF::RAddVC(int n,double alpha,CRowDouble &y,
                     CMatrixDouble &x,int colidx)
  {
   if(x.Rows()==n && y.Size()==n)
      x.Col(colidx,x.Col(colidx)+y*alpha);
   else
     {
      for(int i=0; i<n; i++)
         x.Add(i,colidx,y[i]*alpha);
     }
  }
//+------------------------------------------------------------------+
//| Sets vector X[] to V, reallocating X[] if too small              |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   V        -  value to set                                       |
//|   X        -  possibly preallocated array                        |
//| OUTPUT PARAMETERS:                                               |
//|   X        -  leading N elements are replaced by V; array is     |
//|               reallocated if its length is less than N.          |
//+------------------------------------------------------------------+
void CAblasF::RSetAllocV(int n,double v,CRowDouble &x)
  {
   x=vector<double>::Full(n,v);
  }
//+------------------------------------------------------------------+
//| Sets vector A[] to V, reallocating A[] if too small.             |
//| INPUT PARAMETERS:                                                |
//|   M        -  rows count                                         |
//|   N        -  cols count                                         |
//|   V        -  value to set                                       |
//|   A        -  possibly preallocated matrix                       |
//| OUTPUT PARAMETERS:                                               |
//|   A        -  leading M rows, N cols are replaced by V;          |
//|               the matrix is reallocated if its rows / cols count |
//|               is less than M / N.                                |
//+------------------------------------------------------------------+
void CAblasF::RSetAllocM(int m,int n,double v,CMatrixDouble &a)
  {
   a=matrix<double>::Full(m,n,v);
  }
//+------------------------------------------------------------------+
//| Reallocates X[] if its length is less than required value. Does  |
//| not change its length and contents if it is large enough.        |
//| INPUT PARAMETERS:                                                |
//|   N        -  desired vector length                              |
//|   X        -  possibly preallocated array                        |
//| OUTPUT PARAMETERS:                                               |
//|   X        -  length(X)>=N                                       |
//+------------------------------------------------------------------+
void CAblasF::RAllocV(int n,CRowDouble &x)
  {
   if((int)x.Size()<n)
      x.Resize(n);
  }
//+------------------------------------------------------------------+
//| Reallocates X[] if its length is less than required value. Does  |
//| not change its length and contents if it is large enough.        |
//| INPUT PARAMETERS:                                                |
//|   N        -  desired vector length                              |
//|   X        -  possibly preallocated array                        |
//| OUTPUT PARAMETERS:                                               |
//|   X        -  length(X)>=N                                       |
//+------------------------------------------------------------------+
void CAblasF::IAllocV(int n,CRowInt &x)
  {
   if(x.Size()<n)
     {
      x.Resize(n);
      x.Fill(0);
     }
  }
//+------------------------------------------------------------------+
//| Reallocates X[] if its length is less than required value. Does  |
//| not change its length and contents if it is large enough.        |
//| INPUT PARAMETERS:                                                |
//|   N        -  desired vector length                              |
//|   X        -   possibly preallocated array                       |
//| OUTPUT PARAMETERS:                                               |
//|   X        -  length(X) >= N                                     |
//+------------------------------------------------------------------+
void CAblasF::BAllocV(int n,bool &x[])
  {
   if(CAp::Len(x)<n)
      ArrayResize(x,n);
  }
//+------------------------------------------------------------------+
//| Reallocates matrix if its rows or cols count is less than        |
//| required. Does not change its size if it is exactly that size or |
//| larger.                                                          |
//| INPUT PARAMETERS:                                                |
//|   M        -  rows count                                         |
//|   N        -  cols count                                         |
//|   A        -  possibly preallocated matrix                       |
//| OUTPUT PARAMETERS:                                               |
//|   A        -  size is at least M*N                               |
//+------------------------------------------------------------------+
void CAblasF::RAllocM(int m,int n,CMatrixDouble &a)
  {
   if((int)a.Rows()<m || (int)a.Cols()<n)
      a.Resize(m,n);
  }
//+------------------------------------------------------------------+
//| Sets vector X[] to V, reallocating X[] if too small              |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   V        -  value to set                                       |
//|   X        -  possibly preallocated array                        |
//| OUTPUT PARAMETERS:                                               |
//|   X        -  leading N elements are replaced by V; array is     |
//|               reallocated if its length is less than N.          |
//+------------------------------------------------------------------+
void CAblasF::ISetAllocV(int n,int v,CRowInt &x)
  {
   IAllocV(n,x);
   ISetV(n,v,x);
  }
//+------------------------------------------------------------------+
//| Sets vector X[] to V, reallocating X[] if too small              |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   V        -  value to set                                       |
//|   X        -  possibly preallocated array                        |
//| OUTPUT PARAMETERS:                                               |
//|   X        -  leading N elements are replaced by V; array is     |
//|               reallocated if its length is less than N.          |
//+------------------------------------------------------------------+
void CAblasF::BSetAllocV(int n,bool v,bool &x[])
  {
   BAllocV(n,x);
   BSetV(n,v,x);
  }
//+------------------------------------------------------------------+
//| Sets col J of A[,] to V                                          |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   V        -  value to set                                       |
//|   A        -  array[N,N] or larger                               |
//|   J        -  col index                                          |
//| OUTPUT PARAMETERS:                                               |
//|   A        -  leading N elements of I-th col are replaced by V   |
//+------------------------------------------------------------------+
void CAblasF::RSetC(int n,double v,CMatrixDouble &a,int j)
  {
   a.Col(j,vector<double>::Full(n,v));
  }
//+------------------------------------------------------------------+
//| Copies vector X[] to Y[], resizing Y[] if needed.                |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   X        -  array[N], source                                   |
//|   Y        -  possibly preallocated array[N] (resized if needed) |
//| OUTPUT PARAMETERS:                                               |
//|   Y        -  leading N elements are replaced by X               |
//+------------------------------------------------------------------+
void CAblasF::RCopyAllocV(int n,CRowDouble &x,CRowDouble &y)
  {
   y=x;
   y.Resize(n);
  }
//+------------------------------------------------------------------+
//| Copies matrix X[] to Y[], resizing Y[] if needed. On resize,     |
//| dimensions of Y[] are increased - but not decreased.             |
//| INPUT PARAMETERS:                                                |
//|   M        -  rows count                                         |
//|   N        -  cols count                                         |
//|   X        -  array[M,N], source                                 |
//|   Y        -  possibly preallocated array[M,N](resized if needed)|
//| OUTPUT PARAMETERS:                                               |
//|   Y        -  leading [M,N] elements are replaced by X           |
//+------------------------------------------------------------------+
void CAblasF::RCopyM(int m,int n,CMatrixDouble &x,CMatrixDouble &y)
  {
//--- quick exit
   if(m==0 || n==0)
      return;
//--- check
   if((int)y.Rows()<=m && (int)y.Cols()<=n)
     {
      y=x;
      return;
     }
//--- create variables
   int i=0;
   int j=0;
//--- check
   if((int)y.Rows()>m && (int)y.Cols()<=n)
     {
      RAllocM(m,n,y);
      for(i=0; i<m; i++)
         y.Row(i,x.Row(i)+0);
      return;
     }
//--- check
   if((int)y.Rows()<=m && (int)y.Cols()>n)
     {
      RAllocM(m,n,y);
      for(i=0; i<n; i++)
         y.Col(i,x.Col(i)+0);
      return;
     }

   for(i=0; i<m; i++)
      for(j=0; j<n; j++)
         y.Set(i,j,x.Get(i,j));
  }
//+------------------------------------------------------------------+
//| Copies matrix X[] to Y[], resizing Y[] if needed. On resize,     |
//| dimensions of Y[] are increased - but not decreased.             |
//| INPUT PARAMETERS:                                                |
//|   M        -  rows count                                         |
//|   N        -  cols count                                         |
//|   X        -  array[M,N], source                                 |
//|   Y        -  possibly preallocated array[M,N](resized if needed)|
//| OUTPUT PARAMETERS:                                               |
//|   Y        -  leading [M,N] elements are replaced by X           |
//+------------------------------------------------------------------+
void CAblasF::RCopyAllocM(int m,int n,CMatrixDouble &x,CMatrixDouble &y)
  {
   RCopyM(m,n,x,y);
  }
//+------------------------------------------------------------------+
//| Copies vector X[] to Y[], resizing Y[] if needed.                |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   X        -  array[N], source                                   |
//|   Y        -  possibly preallocated array[N] (resized if needed) |
//| OUTPUT PARAMETERS:                                               |
//|   Y        -  leading N elements are replaced by X               |
//+------------------------------------------------------------------+
void CAblasF::ICopyAllocV(int n,CRowInt &x,CRowInt &y)
  {
   ICopyV(n,x,y);
  }
//+------------------------------------------------------------------+
//| Copies vector X[] to Y[], resizing Y[] if needed.                |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   X        -  array[N], source                                   |
//|   Y        -  possibly preallocated array[N] (resized if needed) |
//| OUTPUT PARAMETERS:                                               |
//|   Y        -  leading N elements are replaced by X               |
//+------------------------------------------------------------------+
void CAblasF::BCopyAllocV(int n,bool &x[],bool &y[])
  {
   ArrayCopy(y,x,0,0,n);
  }
//+------------------------------------------------------------------+
//| Grows X, i.e. changes its size in such a way that:               |
//|   a) contents is preserved                                       |
//|   b) new size is at least N                                      |
//|   c) actual size can be larger than N, so subsequent grow() calls|
//|      can return without reallocation                             |
//+------------------------------------------------------------------+
void CAblasF::IGrowV(int newn,CRowInt &x)
  {
//--- quick exit
   if(x.Size()>=newn)
      return;
//--- create a variable
   int oldn=x.Size();
   newn=MathMax(newn,(int)MathRound(1.8*oldn+1));
   x.Resize(newn);
  }
//+------------------------------------------------------------------+
//| Grows X, i.e. changes its size in such a way that:               |
//|   a) contents is preserved                                       |
//|   b) new size is at least N                                      |
//|   c) actual size can be larger than N, so subsequent grow() calls|
//|      can return without reallocation                             |
//+------------------------------------------------------------------+
void CAblasF::RGrowV(int newn,CRowDouble &x)
  {
//--- quick exit
   if((int)x.Size()>=newn)
      return;
//--- create a variable
   int oldn=(int)x.Size();
   newn=MathMax(newn,(int)MathRound(1.8*oldn+1));
   x.Resize(newn);
  }
//+------------------------------------------------------------------+
//| Performs copying with multiplication of V*X[] to Y[*,J]          |
//| INPUT PARAMETERS:   `                                            |
//|   N        -  vector length                                      |
//|   V        -  multiplier                                         |
//|   X        -  array[N], source                                   |
//|   Y        -  preallocated array[N,?]                            |
//|   CIdx     -  destination rocol index                            |
//| OUTPUT PARAMETERS:                                               |
//|   Y        -  Y[RIdx,...] = V*X                                  |
//+------------------------------------------------------------------+
void CAblasF::RCopyMulVC(int n,double v,CRowDouble &x,CMatrixDouble &y,
                         int cidx)
  {
//--- create variable
   vector<double> temp=x.ToVector();
//--- check
   if(temp.Size()!=n)
      temp.Resize(n);
//--- copy
   y.Col(cidx,temp*v);
  }
//+------------------------------------------------------------------+
//| Copies vector X[] to column J of A[,]                            |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   X        -  array[N], source                                   |
//|   A        -  preallocated 2D array large enough to store result |
//|   J        -  destination col index                              |
//| OUTPUT PARAMETERS:                                               |
//|   A        -  leading N elements of J-th column are replaced by X|
//+------------------------------------------------------------------+
void CAblasF::RCopyVC(int n,CRowDouble &x,CMatrixDouble &a,int j)
  {
//--- create variable
   vector<double> temp=x.ToVector();
//--- check
   if(temp.Size()!=n)
      temp.Resize(n);
//--- copy
   a.Col(j,temp);
  }
//+------------------------------------------------------------------+
//| Copies column J of A[,] to vector X[]                            |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   A        -  source 2D array                                    |
//|   J        -  source col index                                   |
//| OUTPUT PARAMETERS:                                               |
//|   X        -  preallocated array[N], destination                 |
//+------------------------------------------------------------------+
void CAblasF::RCopyCV(int n,CMatrixDouble &a,int j,CRowDouble &x)
  {
   if(x.Size()<=n && a.Rows()>=n)
     {
      x=a.Col(j)+0;
      if(x.Size()!=n)
         x.Resize(n);
     }
   else
     {
      for(int i=0; i<MathMin(n,a.Rows()); i++)
         x.Set(i,a.Get(i,j));
     }
  }
//+------------------------------------------------------------------+
//| CMatrixGEMM kernel, basecase code for CMatrixGEMM.               |
//| This subroutine calculates C = alpha*op1(A)*op2(B) +beta*C where:|
//|   * C is MxN general matrix                                      |
//|   * op1(A) is MxK matrix                                         |
//|   * op2(B) is KxN matrix                                         |
//|  *"op" may be identity transformation, transposition, conjugate|
//|     transposition                                                |
//| Additional info:                                                 |
//|   * multiplication result replaces C. If Beta=0, C elements are  |
//|     not used in calculations (not multiplied by zero - just not  |
//|     referenced)                                                  |
//|   * if Alpha=0, A is not used (not multiplied by zero - just not |
//|     referenced)                                                  |
//|   * if both Beta and Alpha are zero, C is filled by zeros.       |
//| IMPORTANT:                                                       |
//| This function does NOT preallocate output matrix C, it MUST be   |
//| preallocated by caller prior to calling this function. In case C |
//| does not have enough space to store result, exception will be    |
//| generated.                                                       |
//| INPUT PARAMETERS:                                                |
//|   M        -  matrix size, M>0                                   |
//|   N        -  matrix size, N>0                                   |
//|   K        -  matrix size, K>0                                   |
//|   Alpha    -  coefficient                                        |
//|   A        -  matrix                                             |
//|   IA       -  submatrix offset                                   |
//|   JA       -  submatrix offset                                   |
//|   OpTypeA  -  transformation type:                               |
//|               * 0 - no transformation                            |
//|               * 1 - transposition                                |
//|               * 2 - conjugate transposition                      |
//|   B        -  matrix                                             |
//|   IB       -  submatrix offset                                   |
//|   JB       -  submatrix offset                                   |
//|   OpTypeB  -  transformation type:                               |
//|               * 0 - no transformation                            |
//|               * 1 - transposition                                |
//|               * 2 - conjugate transposition                      |
//|   Beta     -  coefficient                                        |
//|   C        -  PREALLOCATED output matrix                         |
//|   IC       -  submatrix offset                                   |
//|   JC       -  submatrix offset                                   |
//+------------------------------------------------------------------+
void CAblasF::CMatrixGemmK(int m,int n,int k,complex alpha,
                           const CMatrixComplex &a,int ia,int ja,int optypea,
                           const CMatrixComplex &b,int ib,int jb,int optypeb,
                           complex beta,CMatrixComplex &c,int ic,int jc)
  {
//--- if matrix size is zero
   if(m==0 || n==0)
      return;
//--- if K=0 or Alpha=0, then C=Beta*C
   if(beta!=1.0)
     {
      if(beta!=0.0)
        {
         for(int i=0; i<m; i++)
            for(int j=0; j<n ; j++)
               c.Set(ic+i,jc+j,beta*c.Get(ic+i,jc+j));
        }
      else
        {
         for(int i=0; i<m; i++)
            for(int j=0; j<n; j++)
               c.Set(ic+i,jc+j,0.0);
        }
     }
   if(k==0 || alpha==0)
      return;
//--- create variables
   int     i=0;
   int     j=0;
   complex v=0;
   complex v00=0;
   complex v01=0;
   complex v10=0;
   complex v11=0;
   double  v00x=0;
   double  v00y=0;
   double  v01x=0;
   double  v01y=0;
   double  v10x=0;
   double  v10y=0;
   double  v11x=0;
   double  v11y=0;
   double  a0x=0;
   double  a0y=0;
   double  a1x=0;
   double  a1y=0;
   double  b0x=0;
   double  b0y=0;
   double  b1x=0;
   double  b1y=0;
   int     idxa0=0;
   int     idxa1=0;
   int     idxb0=0;
   int     idxb1=0;
   int     i0=0;
   int     i1=0;
   int     ik=0;
   int     j0=0;
   int     j1=0;
   int     jk=0;
   int     t=0;
   int     offsa=0;
   int     offsb=0;
   int     i_=0;
   int     i1_=0;
//--- General case
   for(i=0; i<m; i+=2)
     {
      for(j=0; j<n; j+=2)
        {
         //--- Choose between specialized 4x4 code and general code
         if(i+2<=m && j+2<=n)
           {
            //--- Specialized 4x4 code for [I..I+3]x[J..J+3] submatrix of C.
            //--- This submatrix is calculated as sum of K rank-1 products,
            //--- with operands cached in local variables in order to speed
            //--- up operations with arrays.
            v00x=0.0;
            v00y=0.0;
            v01x=0.0;
            v01y=0.0;
            v10x=0.0;
            v10y=0.0;
            v11x=0.0;
            v11y=0.0;
            if(optypea==0)
              {
               idxa0=ia+i+0;
               idxa1=ia+i+1;
               offsa=ja;
              }
            else
              {
               idxa0=ja+i+0;
               idxa1=ja+i+1;
               offsa=ia;
              }
            if(optypeb==0)
              {
               idxb0=jb+j+0;
               idxb1=jb+j+1;
               offsb=ib;
              }
            else
              {
               idxb0=ib+j+0;
               idxb1=ib+j+1;
               offsb=jb;
              }
            for(t=0; t<k; t++)
              {
               switch(optypea)
                 {
                  case 0:
                     a0x=a.Get(idxa0,offsa).real;
                     a0y=a.Get(idxa0,offsa).imag;
                     a1x=a.Get(idxa1,offsa).real;
                     a1y=a.Get(idxa1,offsa).imag;
                     break;
                  case 1:
                     a0x=a.Get(offsa,idxa0).real;
                     a0y=a.Get(offsa,idxa0).imag;
                     a1x=a.Get(offsa,idxa1).real;
                     a1y=a.Get(offsa,idxa1).imag;
                     break;
                  case 2:
                     a0x=a.Get(offsa,idxa0).real;
                     a0y=-a.Get(offsa,idxa0).imag;
                     a1x=a.Get(offsa,idxa1).real;
                     a1y=-a.Get(offsa,idxa1).imag;
                     break;
                 }
               switch(optypeb)
                 {
                  case 0:
                     b0x=b.Get(offsb,idxb0).real;
                     b0y=b.Get(offsb,idxb0).imag;
                     b1x=b.Get(offsb,idxb1).real;
                     b1y=b.Get(offsb,idxb1).imag;
                     break;
                  case 1:
                     b0x=b.Get(idxb0,offsb).real;
                     b0y=b.Get(idxb0,offsb).imag;
                     b1x=b.Get(idxb1,offsb).real;
                     b1y=b.Get(idxb1,offsb).imag;
                     break;
                  case 2:
                     b0x=b.Get(idxb0,offsb).real;
                     b0y=-b.Get(idxb0,offsb).imag;
                     b1x=b.Get(idxb1,offsb).real;
                     b1y=-b.Get(idxb1,offsb).imag;
                     break;
                 }
               v00x+=a0x*b0x-a0y*b0y;
               v00y+=a0x*b0y+a0y*b0x;
               v01x+=a0x*b1x-a0y*b1y;
               v01y+=a0x*b1y+a0y*b1x;
               v10x+=a1x*b0x-a1y*b0y;
               v10y+=a1x*b0y+a1y*b0x;
               v11x+=a1x*b1x-a1y*b1y;
               v11y+=a1x*b1y+a1y*b1x;
               offsa++;
               offsb++;
              }
            v00.real=v00x;
            v00.imag=v00y;
            v10.real=v10x;
            v10.imag=v10y;
            v01.real=v01x;
            v01.imag=v01y;
            v11.real=v11x;
            v11.imag=v11y;
            if(beta==0)
              {
               c.Set(ic+i,jc+j,alpha*v00);
               c.Set(ic+i,jc+j+1,alpha*v01);
               c.Set(ic+i+1,jc+j,alpha*v10);
               c.Set(ic+i+1,jc+j+1,alpha*v11);
              }
            else
              {
               c.Set(ic+i,jc+j,c.Get(ic+i,jc+j)+alpha*v00);
               c.Set(ic+i,jc+j+1,c.Get(ic+i,jc+j+1)+alpha*v01);
               c.Set(ic+i+1,jc+j,c.Get(ic+i+1,jc+j)+alpha*v10);
               c.Set(ic+i+1,jc+j+1,c.Get(ic+i+1,jc+j+1)+alpha*v11);
              }
           }
         else
           {
            //--- Determine submatrix [I0..I1]x[J0..J1] to process
            i0=i;
            i1=MathMin(i+1,m-1);
            j0=j;
            j1=MathMin(j+1,n-1);
            //--- Process submatrix
            for(ik=i0; ik<=i1; ik++)
              {
               for(jk=j0; jk<=j1; jk++)
                 {
                  v=0.0;
                  switch(optypea)
                    {
                     case 0:
                        switch(optypeb)
                          {
                           case 0:
                              i1_=(ib)-(ja);
                              for(i_=ja; i_<ja+k; i_++)
                                 v+=a.Get(ia+ik,i_)*b.Get(i_+i1_,jb+jk);
                              break;
                           case 1:
                              i1_=(jb)-(ja);
                              for(i_=ja; i_<ja+k; i_++)
                                 v+=a.Get(ia+ik,i_)*b.Get(ib+jk,i_+i1_);
                              break;
                           case 2:
                              i1_=(jb)-(ja);
                              for(i_=ja; i_<ja+k; i_++)
                                 v+=a.Get(ia+ik,i_)*CMath::Conj(b.Get(ib+jk,i_+i1_));
                              break;
                          }
                        break;
                     case 1:
                        switch(optypeb)
                          {
                           case 0:
                              i1_=(ib)-(ia);
                              for(i_=ia; i_<ia+k; i_++)
                                 v+=a.Get(i_,ja+ik)*b.Get(i_+i1_,jb+jk);
                              break;
                           case 1:
                              i1_=(jb)-(ia);
                              for(i_=ia; i_<ia+k; i_++)
                                 v+=a.Get(i_,ja+ik)*b.Get(ib+jk,i_+i1_);
                              break;
                           case 2:
                              i1_=(jb)-(ia);
                              for(i_=ia; i_<ia+k; i_++)
                                 v+=a.Get(i_,ja+ik)*CMath::Conj(b.Get(ib+jk,i_+i1_));
                          }
                        break;
                     case 2:
                        switch(optypeb)
                          {
                           case 0:
                              i1_=(ib)-(ia);
                              for(i_=ia; i_<ia+k; i_++)
                                 v+=CMath::Conj(a.Get(i_,ja+ik))*b.Get(i_+i1_,jb+jk);
                              break;
                           case 1:
                              i1_=(jb)-(ia);
                              for(i_=ia; i_<ia+k; i_++)
                                 v+=CMath::Conj(a.Get(i_,ja+ik))*b.Get(ib+jk,i_+i1_);
                              break;
                           case 2:
                              i1_=(jb)-(ia);
                              for(i_=ia; i_<ia+k; i_++)
                                 v+=CMath::Conj(a.Get(i_,ja+ik))*CMath::Conj(b.Get(ib+jk,i_+i1_));
                              break;
                          }
                        break;
                    }
                  if(beta==0)
                     c.Set(ic+ik,jc+jk,alpha*v);
                  else
                     c.Set(ic+ik,jc+jk,c.Get(ic+ik,jc+jk)+alpha*v);
                 }   // end for(jk)
              }   // end for(ik)
           }   // end else
        }   // end for(j)
     } // end for(i)
  }
//+------------------------------------------------------------------+
//| RMatrixGEMM kernel, basecase code for RMatrixGEMM.               |
//| This subroutine calculates C = alpha*op1(A)*op2(B) +beta*C where:|
//|   * C is MxN general matrix                                      |
//|   * op1(A) is MxK matrix                                         |
//|   * op2(B) is KxN matrix                                         |
//|  *"op" may be identity transformation, transposition           |
//| Additional info:                                                 |
//|   * multiplication result replaces C. If Beta=0, C elements are  |
//|     not used in calculations (not multiplied by zero - just not  |
//|     referenced)                                                  |
//|   * if Alpha=0, A is not used (not multiplied by zero - just not |
//|     referenced)                                                  |
//|   * if both Beta and Alpha are zero, C is filled by zeros.       |
//| IMPORTANT:                                                       |
//| This function does NOT preallocate output matrix C, it MUST be   |
//| preallocated by caller prior to calling this function. In case C |
//| does not have enough space to store result, exception will be    |
//| generated.                                                       |
//| INPUT PARAMETERS:                                                |
//|   M        -  matrix size, M>0                                   |
//|   N        -  matrix size, N>0                                   |
//|   K        -  matrix size, K>0                                   |
//|   Alpha    -  coefficient                                        |
//|   A        -  matrix                                             |
//|   IA       -  submatrix offset                                   |
//|   JA       -  submatrix offset                                   |
//|   OpTypeA  -  transformation type:                               |
//|               * 0 - no transformation                            |
//|               * 1 - transposition                                |
//|   B        -  matrix                                             |
//|   IB       -  submatrix offset                                   |
//|   JB       -  submatrix offset                                   |
//|   OpTypeB  -  transformation type:                               |
//|               * 0 - no transformation                            |
//|               * 1 - transposition                                |
//|   Beta     -  coefficient                                        |
//|   C        -  PREALLOCATED output matrix                         |
//|   IC       -  submatrix offset                                   |
//|   JC       -  submatrix offset                                   |
//+------------------------------------------------------------------+
void CAblasF::RMatrixGemmK(int m,int n,int k,double alpha,
                           const CMatrixDouble &a,int ia,int ja,int optypea,
                           const CMatrixDouble &b,int ib,int jb,int optypeb,
                           double beta,CMatrixDouble &c,int ic,int jc)
  {
//--- if matrix size is zero
   if(m==0 || n==0)
      return;
//--- Call specialized code.
//--- NOTE: specialized code was moved to separate function because of strange
//---       issues with instructions cache on some systems; Having too long
//---       functions significantly slows down internal loop of the algorithm.
//--- if K=0 or Alpha=0, then C=Beta*C
   if(k==0 || (double)(alpha)==0.0)
     {
      if(beta==1.0)
         return;
      if(beta!=0.0)
        {
         for(int i=0; i<m; i++)
            for(int j=0; j<n ; j++)
               c.Set(ic+i,jc+j,beta*c.Get(ic+i,jc+j));
        }
      else
        {
         for(int i=0; i<m; i++)
            for(int j=0; j<n; j++)
               c.Set(ic+i,jc+j,0);
        }
      return;
     }

   if(optypea==0)
     {
      if(optypeb==0)
         RMatrixGemmK44v00(m,n,k,alpha,a,ia,ja,b,ib,jb,beta,c,ic,jc);
      else
         RMatrixGemmK44v01(m,n,k,alpha,a,ia,ja,b,ib,jb,beta,c,ic,jc);
     }
   else
      if(optypeb==0)
         RMatrixGemmK44v10(m,n,k,alpha,a,ia,ja,b,ib,jb,beta,c,ic,jc);
      else
         RMatrixGemmK44v11(m,n,k,alpha,a,ia,ja,b,ib,jb,beta,c,ic,jc);
  }
//+------------------------------------------------------------------+
//| RMatrixGEMM kernel, basecase code for RMatrixGEMM, specialized   |
//| for sitation with OpTypeA=0 and OpTypeB=0.                       |
//| Additional info:                                                 |
//|   * this function requires that Alpha<>0 (assertion is thrown    |
//|     otherwise)                                                   |
//| INPUT PARAMETERS:                                                |
//|   M        -  matrix size, M>0                                   |
//|   N        -  matrix size, N>0                                   |
//|   K        -  matrix size, K>0                                   |
//|   Alpha    -  coefficient                                        |
//|   A        -  matrix                                             |
//|   IA       -  submatrix offset                                   |
//|   JA       -  submatrix offset                                   |
//|   B        -  matrix                                             |
//|   IB       -  submatrix offset                                   |
//|   JB       -  submatrix offset                                   |
//|   Beta     -  coefficient                                        |
//|   C        -  PREALLOCATED output matrix                         |
//|   IC       -  submatrix offset                                   |
//|   JC       -  submatrix offset                                   |
//+------------------------------------------------------------------+
void CAblasF::RMatrixGemmK44v00(int m,int n,int k,double alpha,
                                const CMatrixDouble &a,int ia,int ja,
                                const CMatrixDouble &b,int ib,int jb,
                                double beta,CMatrixDouble &c,int ic,int jc)
  {
//--- check
   if(!CAp::Assert(alpha!=0.0,__FUNCTION__": internal error (Alpha=0)"))
      return;
//--- if matrix size is zero
   if(m==0 || n==0)
      return;
//--- create variables
   int    i=0;
   int    j=0;
   double v=0;
   double v00=0;
   double v01=0;
   double v02=0;
   double v03=0;
   double v10=0;
   double v11=0;
   double v12=0;
   double v13=0;
   double v20=0;
   double v21=0;
   double v22=0;
   double v23=0;
   double v30=0;
   double v31=0;
   double v32=0;
   double v33=0;
   double a0=0;
   double a1=0;
   double a2=0;
   double a3=0;
   double b0=0;
   double b1=0;
   double b2=0;
   double b3=0;
   int    idxa0=0;
   int    idxa1=0;
   int    idxa2=0;
   int    idxa3=0;
   int    idxb0=0;
   int    idxb1=0;
   int    idxb2=0;
   int    idxb3=0;
   int    i0=0;
   int    i1=0;
   int    ik=0;
   int    j0=0;
   int    j1=0;
   int    jk=0;
   int    t=0;
   int    offsa=0;
   int    offsb=0;
   int    i_=0;
   int    i1_=0;
//--- A*B
   for(i=0; i<m; i+=4)
     {
      for(j=0; j<n; j+=4)
        {
         //--- Choose between specialized 4x4 code and general code
         if(i+4<=m && j+4<=n)
           {
            //--- Specialized 4x4 code for [I..I+3]x[J..J+3] submatrix of C.
            //--- This submatrix is calculated as sum of K rank-1 products,
            //--- with operands cached in local variables in order to speed
            //--- up operations with arrays.
            idxa0=ia+i;
            idxa1=idxa0+1;
            idxa2=idxa0+2;
            idxa3=idxa0+3;
            offsa=ja;
            idxb0=jb+j;
            idxb1=idxb0+1;
            idxb2=idxb0+2;
            idxb3=idxb0+3;
            offsb=ib;
            v00=0.0;
            v01=0.0;
            v02=0.0;
            v03=0.0;
            v10=0.0;
            v11=0.0;
            v12=0.0;
            v13=0.0;
            v20=0.0;
            v21=0.0;
            v22=0.0;
            v23=0.0;
            v30=0.0;
            v31=0.0;
            v32=0.0;
            v33=0.0;
            //--- Different variants of internal loop
            for(t=0; t<k; t++)
              {
               a0=a.Get(idxa0,offsa);
               a1=a.Get(idxa1,offsa);
               b0=b.Get(offsb,idxb0);
               b1=b.Get(offsb,idxb1);
               v00+=a0*b0;
               v01+=a0*b1;
               v10+=a1*b0;
               v11+=a1*b1;
               a2=a.Get(idxa2,offsa);
               a3=a.Get(idxa3,offsa);
               v20+=a2*b0;
               v21+=a2*b1;
               v30+=a3*b0;
               v31+=a3*b1;
               b2=b.Get(offsb,idxb2);
               b3=b.Get(offsb,idxb3);
               v22+=a2*b2;
               v23+=a2*b3;
               v32+=a3*b2;
               v33+=a3*b3;
               v02+=a0*b2;
               v03+=a0*b3;
               v12+=a1*b2;
               v13+=a1*b3;
               offsa++;
               offsb++;
              }
            if(beta==0.0)
              {
               idxa0=ic+i;
               idxb0=jc+j;
               c.Set(idxa0,idxb0,alpha*v00);
               c.Set(idxa0,idxb0+1,alpha*v01);
               c.Set(idxa0,idxb0+2,alpha*v02);
               c.Set(idxa0,idxb0+3,alpha*v03);
               idxa0++;
               c.Set(idxa0,idxb0,alpha*v10);
               c.Set(idxa0,idxb0+1,alpha*v11);
               c.Set(idxa0,idxb0+2,alpha*v12);
               c.Set(idxa0,idxb0+3,alpha*v13);
               idxa0++;
               c.Set(idxa0,idxb0,alpha*v20);
               c.Set(idxa0,idxb0+1,alpha*v21);
               c.Set(idxa0,idxb0+2,alpha*v22);
               c.Set(idxa0,idxb0+3,alpha*v23);
               idxa0++;
               c.Set(idxa0,idxb0,alpha*v30);
               c.Set(idxa0,idxb0+1,alpha*v31);
               c.Set(idxa0,idxb0+2,alpha*v32);
               c.Set(idxa0,idxb0+3,alpha*v33);
              }
            else
              {
               idxa0=ic+i;
               idxb0=jc+j;
               c.Set(idxa0,idxb0,beta*c.Get(idxa0,idxb0)+alpha*v00);
               c.Set(idxa0,idxb0+1,beta*c.Get(idxa0,idxb0+1)+alpha*v01);
               c.Set(idxa0,idxb0+2,beta*c.Get(idxa0,idxb0+2)+alpha*v02);
               c.Set(idxa0,idxb0+3,beta*c.Get(idxa0,idxb0+3)+alpha*v03);
               idxa0++;
               c.Set(idxa0,idxb0,beta*c.Get(idxa0,idxb0)+alpha*v10);
               c.Set(idxa0,idxb0+1,beta*c.Get(idxa0,idxb0+1)+alpha*v11);
               c.Set(idxa0,idxb0+2,beta*c.Get(idxa0,idxb0+2)+alpha*v12);
               c.Set(idxa0,idxb0+3,beta*c.Get(idxa0,idxb0+3)+alpha*v13);
               idxa0++;
               c.Set(idxa0,idxb0,beta*c.Get(idxa0,idxb0)+alpha*v20);
               c.Set(idxa0,idxb0+1,beta*c.Get(idxa0,idxb0+1)+alpha*v21);
               c.Set(idxa0,idxb0+2,beta*c.Get(idxa0,idxb0+2)+alpha*v22);
               c.Set(idxa0,idxb0+3,beta*c.Get(idxa0,idxb0+3)+alpha*v23);
               idxa0++;
               c.Set(idxa0,idxb0,beta*c.Get(idxa0,idxb0)+alpha*v30);
               c.Set(idxa0,idxb0+1,beta*c.Get(idxa0,idxb0+1)+alpha*v31);
               c.Set(idxa0,idxb0+2,beta*c.Get(idxa0,idxb0+2)+alpha*v32);
               c.Set(idxa0,idxb0+3,beta*c.Get(idxa0,idxb0+3)+alpha*v33);
              }
           }
         else
           {
            //--- Determine submatrix [I0..I1]x[J0..J1] to process
            i0=i;
            i1=MathMin(i+3,m-1);
            j0=j;
            j1=MathMin(j+3,n-1);
            //--- Process submatrix
            for(ik=i0; ik<=i1; ik++)
              {
               for(jk=j0; jk<=j1; jk++)
                 {
                  v=0.0;
                  if(k!=0 && alpha!=0.0)
                    {
                     i1_=(ib)-(ja);
                     for(i_=ja; i_<ja+k; i_++)
                        v+=a.Get(ia+ik,i_)*b.Get(i_+i1_,jb+jk);
                    }
                  if(beta==0.0)
                     c.Set(ic+ik,jc+jk,alpha*v);
                  else
                     c.Set(ic+ik,jc+jk,beta*c.Get(ic+ik,jc+jk)+alpha*v);
                 }
              }
           }   // else
        }   // end for(j)
     }   // end for(i)
  }
//+------------------------------------------------------------------+
//| RMatrixGEMM kernel, basecase code for RMatrixGEMM, specialized   |
//| for sitation with OpTypeA=0 and OpTypeB=1.                       |
//| Additional info:                                                 |
//|   * this function requires that Alpha<>0 (assertion is thrown    |
//|     otherwise)                                                   |
//| INPUT PARAMETERS:                                                |
//|   M        -  matrix size, M>0                                   |
//|   N        -  matrix size, N>0                                   |
//|   K        -  matrix size, K>0                                   |
//|   Alpha    -  coefficient                                        |
//|   A        -  matrix                                             |
//|   IA       -  submatrix offset                                   |
//|   JA       -  submatrix offset                                   |
//|   B        -  matrix                                             |
//|   IB       -  submatrix offset                                   |
//|   JB       -  submatrix offset                                   |
//|   Beta     -  coefficient                                        |
//|   C        -  PREALLOCATED output matrix                         |
//|   IC       -  submatrix offset                                   |
//|   JC       -  submatrix offset                                   |
//+------------------------------------------------------------------+
void CAblasF::RMatrixGemmK44v01(int m,int n,int k,double alpha,
                                const CMatrixDouble &a,int ia,int ja,
                                const CMatrixDouble &b,int ib,int jb,
                                double beta,CMatrixDouble &c,int ic,int jc)
  {
//--- check
   if(!CAp::Assert(alpha!=0.0,__FUNCTION__": internal error (Alpha=0)"))
      return;
//--- if matrix size is zero
   if(m==0 || n==0)
      return;
//--- create variables
   int    i=0;
   int    j=0;
   double v=0;
   double v00=0;
   double v01=0;
   double v02=0;
   double v03=0;
   double v10=0;
   double v11=0;
   double v12=0;
   double v13=0;
   double v20=0;
   double v21=0;
   double v22=0;
   double v23=0;
   double v30=0;
   double v31=0;
   double v32=0;
   double v33=0;
   double a0=0;
   double a1=0;
   double a2=0;
   double a3=0;
   double b0=0;
   double b1=0;
   double b2=0;
   double b3=0;
   int    idxa0=0;
   int    idxa1=0;
   int    idxa2=0;
   int    idxa3=0;
   int    idxb0=0;
   int    idxb1=0;
   int    idxb2=0;
   int    idxb3=0;
   int    i0=0;
   int    i1=0;
   int    ik=0;
   int    j0=0;
   int    j1=0;
   int    jk=0;
   int    t=0;
   int    offsa=0;
   int    offsb=0;
   int    i_=0;
   int    i1_=0;
//--- A*B'
   for(i=0; i<m; i+=4)
     {
      for(j=0; j<n; j+=4)
        {
         //--- Choose between specialized 4x4 code and general code
         if(i+4<=m && j+4<=n)
           {
            //--- Specialized 4x4 code for [I..I+3]x[J..J+3] submatrix of C.
            //--- This submatrix is calculated as sum of K rank-1 products,
            //--- with operands cached in local variables in order to speed
            //--- up operations with arrays.
            idxa0=ia+i;
            idxa1=idxa0+1;
            idxa2=idxa0+2;
            idxa3=idxa0+3;
            offsa=ja;
            idxb0=ib+j;
            idxb1=idxb0+1;
            idxb2=idxb0+2;
            idxb3=idxb0+3;
            offsb=jb;
            v00=0.0;
            v01=0.0;
            v02=0.0;
            v03=0.0;
            v10=0.0;
            v11=0.0;
            v12=0.0;
            v13=0.0;
            v20=0.0;
            v21=0.0;
            v22=0.0;
            v23=0.0;
            v30=0.0;
            v31=0.0;
            v32=0.0;
            v33=0.0;
            for(t=0; t<k; t++)
              {
               a0=a.Get(idxa0,offsa);
               a1=a.Get(idxa1,offsa);
               b0=b.Get(idxb0,offsb);
               b1=b.Get(idxb1,offsb);
               v00+=a0*b0;
               v01+=a0*b1;
               v10+=a1*b0;
               v11+=a1*b1;
               a2=a.Get(idxa2,offsa);
               a3=a.Get(idxa3,offsa);
               v20+=a2*b0;
               v21+=a2*b1;
               v30+=a3*b0;
               v31+=a3*b1;
               b2=b.Get(idxb2,offsb);
               b3=b.Get(idxb3,offsb);
               v22+=a2*b2;
               v23+=a2*b3;
               v32+=a3*b2;
               v33+=a3*b3;
               v02+=a0*b2;
               v03+=a0*b3;
               v12+=a1*b2;
               v13+=a1*b3;
               offsa++;
               offsb++;
              }
            if(beta==0.0)
              {
               idxa0=ic+i;
               idxb0=jc+j;
               c.Set(idxa0,idxb0,alpha*v00);
               c.Set(idxa0,idxb0+1,alpha*v01);
               c.Set(idxa0,idxb0+2,alpha*v02);
               c.Set(idxa0,idxb0+3,alpha*v03);
               idxa0++;
               c.Set(idxa0,idxb0,alpha*v10);
               c.Set(idxa0,idxb0+1,alpha*v11);
               c.Set(idxa0,idxb0+2,alpha*v12);
               c.Set(idxa0,idxb0+3,alpha*v13);
               idxa0++;
               c.Set(idxa0,idxb0,alpha*v20);
               c.Set(idxa0,idxb0+1,alpha*v21);
               c.Set(idxa0,idxb0+2,alpha*v22);
               c.Set(idxa0,idxb0+3,alpha*v23);
               idxa0++;
               c.Set(idxa0,idxb0,alpha*v30);
               c.Set(idxa0,idxb0+1,alpha*v31);
               c.Set(idxa0,idxb0+2,alpha*v32);
               c.Set(idxa0,idxb0+3,alpha*v33);
              }
            else
              {
               idxa0=ic+i;
               idxb0=jc+j;
               c.Set(idxa0,idxb0,beta*c.Get(idxa0,idxb0)+alpha*v00);
               c.Set(idxa0,idxb0+1,beta*c.Get(idxa0,idxb0+1)+alpha*v01);
               c.Set(idxa0,idxb0+2,beta*c.Get(idxa0,idxb0+2)+alpha*v02);
               c.Set(idxa0,idxb0+3,beta*c.Get(idxa0,idxb0+3)+alpha*v03);
               idxa0++;
               c.Set(idxa0,idxb0,beta*c.Get(idxa0,idxb0)+alpha*v10);
               c.Set(idxa0,idxb0+1,beta*c.Get(idxa0,idxb0+1)+alpha*v11);
               c.Set(idxa0,idxb0+2,beta*c.Get(idxa0,idxb0+2)+alpha*v12);
               c.Set(idxa0,idxb0+3,beta*c.Get(idxa0,idxb0+3)+alpha*v13);
               idxa0++;
               c.Set(idxa0,idxb0,beta*c.Get(idxa0,idxb0)+alpha*v20);
               c.Set(idxa0,idxb0+1,beta*c.Get(idxa0,idxb0+1)+alpha*v21);
               c.Set(idxa0,idxb0+2,beta*c.Get(idxa0,idxb0+2)+alpha*v22);
               c.Set(idxa0,idxb0+3,beta*c.Get(idxa0,idxb0+3)+alpha*v23);
               idxa0++;
               c.Set(idxa0,idxb0,beta*c.Get(idxa0,idxb0)+alpha*v30);
               c.Set(idxa0,idxb0+1,beta*c.Get(idxa0,idxb0+1)+alpha*v31);
               c.Set(idxa0,idxb0+2,beta*c.Get(idxa0,idxb0+2)+alpha*v32);
               c.Set(idxa0,idxb0+3,beta*c.Get(idxa0,idxb0+3)+alpha*v33);
              }
           }
         else
           {
            //--- Determine submatrix [I0..I1]x[J0..J1] to process
            i0=i;
            i1=MathMin(i+3,m-1);
            j0=j;
            j1=MathMin(j+3,n-1);
            //--- Process submatrix
            for(ik=i0; ik<=i1; ik++)
              {
               for(jk=j0; jk<=j1; jk++)
                 {
                  v=0;
                  if(k!=0 && alpha!=0.0)
                    {
                     i1_=(jb)-(ja);
                     for(i_=ja; i_<ja+k; i_++)
                        v+=a.Get(ia+ik,i_)*b.Get(ib+jk,i_+i1_);
                    }
                  if(beta==0.0)
                     c.Set(ic+ik,jc+jk,alpha*v);
                  else
                     c.Set(ic+ik,jc+jk,beta*c.Get(ic+ik,jc+jk)+alpha*v);
                 }
              }
           }   // else
        }   // end for(j)
     }   // end for(i)
  }
//+------------------------------------------------------------------+
//| RMatrixGEMM kernel, basecase code for RMatrixGEMM, specialized   |
//| for sitation with OpTypeA=1 and OpTypeB=0.                       |
//| Additional info:                                                 |
//|   * this function requires that Alpha<>0 (assertion is thrown    |
//|     otherwise)                                                   |
//| INPUT PARAMETERS:                                                |
//|   M        -  matrix size, M>0                                   |
//|   N        -  matrix size, N>0                                   |
//|   K        -  matrix size, K>0                                   |
//|   Alpha    -  coefficient                                        |
//|   A        -  matrix                                             |
//|   IA       -  submatrix offset                                   |
//|   JA       -  submatrix offset                                   |
//|   B        -  matrix                                             |
//|   IB       -  submatrix offset                                   |
//|   JB       -  submatrix offset                                   |
//|   Beta     -  coefficient                                        |
//|   C        -  PREALLOCATED output matrix                         |
//|   IC       -  submatrix offset                                   |
//|   JC       -  submatrix offset                                   |
//+------------------------------------------------------------------+
void CAblasF::RMatrixGemmK44v10(int m,int n,int k,double alpha,
                                const CMatrixDouble &a,int ia,int ja,
                                const CMatrixDouble &b,int ib,int jb,
                                double beta,CMatrixDouble &c,int ic,int jc)
  {
//--- check
   if(!CAp::Assert(alpha!=0.0,__FUNCTION__": internal error (Alpha=0)"))
      return;
//--- if matrix size is zero
   if(m==0 || n==0)
      return;
//--- create variable
   int    i=0;
   int    j=0;
   double v=0;
   double v00=0;
   double v01=0;
   double v02=0;
   double v03=0;
   double v10=0;
   double v11=0;
   double v12=0;
   double v13=0;
   double v20=0;
   double v21=0;
   double v22=0;
   double v23=0;
   double v30=0;
   double v31=0;
   double v32=0;
   double v33=0;
   double a0=0;
   double a1=0;
   double a2=0;
   double a3=0;
   double b0=0;
   double b1=0;
   double b2=0;
   double b3=0;
   int    idxa0=0;
   int    idxa1=0;
   int    idxa2=0;
   int    idxa3=0;
   int    idxb0=0;
   int    idxb1=0;
   int    idxb2=0;
   int    idxb3=0;
   int    i0=0;
   int    i1=0;
   int    ik=0;
   int    j0=0;
   int    j1=0;
   int    jk=0;
   int    t=0;
   int    offsa=0;
   int    offsb=0;
   int    i_=0;
   int    i1_=0;
//--- A'*B
   for(i=0; i<m; i+=4)
     {
      for(j=0; j<n; j+=4)
        {
         //--- Choose between specialized 4x4 code and general code
         if(i+4<=m && j+4<=n)
           {
            //--- Specialized 4x4 code for [I..I+3]x[J..J+3] submatrix of C.
            //--- This submatrix is calculated as sum of K rank-1 products,
            //--- with operands cached in local variables in order to speed
            //--- up operations with arrays.
            idxa0=ja+i;
            idxa1=idxa0+1;
            idxa2=idxa0+2;
            idxa3=idxa0+3;
            offsa=ia;
            idxb0=jb+j;
            idxb1=idxb0+1;
            idxb2=idxb0+2;
            idxb3=idxb0+3;
            offsb=ib;
            v00=0.0;
            v01=0.0;
            v02=0.0;
            v03=0.0;
            v10=0.0;
            v11=0.0;
            v12=0.0;
            v13=0.0;
            v20=0.0;
            v21=0.0;
            v22=0.0;
            v23=0.0;
            v30=0.0;
            v31=0.0;
            v32=0.0;
            v33=0.0;
            for(t=0; t<k; t++)
              {
               a0=a.Get(offsa,idxa0);
               a1=a.Get(offsa,idxa1);
               b0=b.Get(offsb,idxb0);
               b1=b.Get(offsb,idxb1);
               v00+=a0*b0;
               v01+=a0*b1;
               v10+=a1*b0;
               v11+=a1*b1;
               a2=a.Get(offsa,idxa2);
               a3=a.Get(offsa,idxa3);
               v20+=a2*b0;
               v21+=a2*b1;
               v30+=a3*b0;
               v31+=a3*b1;
               b2=b.Get(offsb,idxb2);
               b3=b.Get(offsb,idxb3);
               v22+=a2*b2;
               v23+=a2*b3;
               v32+=a3*b2;
               v33+=a3*b3;
               v02+=a0*b2;
               v03+=a0*b3;
               v12+=a1*b2;
               v13+=a1*b3;
               offsa++;
               offsb++;
              }
            if(beta==0.0)
              {
               idxa0=ic+i;
               idxb0=jc+j;
               c.Set(idxa0,idxb0,alpha*v00);
               c.Set(idxa0,idxb0+1,alpha*v01);
               c.Set(idxa0,idxb0+2,alpha*v02);
               c.Set(idxa0,idxb0+3,alpha*v03);
               idxa0++;
               c.Set(idxa0,idxb0,alpha*v10);
               c.Set(idxa0,idxb0+1,alpha*v11);
               c.Set(idxa0,idxb0+2,alpha*v12);
               c.Set(idxa0,idxb0+3,alpha*v13);
               idxa0++;
               c.Set(idxa0,idxb0,alpha*v20);
               c.Set(idxa0,idxb0+1,alpha*v21);
               c.Set(idxa0,idxb0+2,alpha*v22);
               c.Set(idxa0,idxb0+3,alpha*v23);
               idxa0++;
               c.Set(idxa0,idxb0,alpha*v30);
               c.Set(idxa0,idxb0+1,alpha*v31);
               c.Set(idxa0,idxb0+2,alpha*v32);
               c.Set(idxa0,idxb0+3,alpha*v33);
              }
            else
              {
               idxa0=ic+i;
               idxb0=jc+j;
               c.Set(idxa0,idxb0,beta*c.Get(idxa0,idxb0)+alpha*v00);
               c.Set(idxa0,idxb0+1,beta*c.Get(idxa0,idxb0+1)+alpha*v01);
               c.Set(idxa0,idxb0+2,beta*c.Get(idxa0,idxb0+2)+alpha*v02);
               c.Set(idxa0,idxb0+3,beta*c.Get(idxa0,idxb0+3)+alpha*v03);
               idxa0++;
               c.Set(idxa0,idxb0,beta*c.Get(idxa0,idxb0)+alpha*v10);
               c.Set(idxa0,idxb0+1,beta*c.Get(idxa0,idxb0+1)+alpha*v11);
               c.Set(idxa0,idxb0+2,beta*c.Get(idxa0,idxb0+2)+alpha*v12);
               c.Set(idxa0,idxb0+3,beta*c.Get(idxa0,idxb0+3)+alpha*v13);
               idxa0++;
               c.Set(idxa0,idxb0,beta*c.Get(idxa0,idxb0)+alpha*v20);
               c.Set(idxa0,idxb0+1,beta*c.Get(idxa0,idxb0+1)+alpha*v21);
               c.Set(idxa0,idxb0+2,beta*c.Get(idxa0,idxb0+2)+alpha*v22);
               c.Set(idxa0,idxb0+3,beta*c.Get(idxa0,idxb0+3)+alpha*v23);
               idxa0++;
               c.Set(idxa0,idxb0,beta*c.Get(idxa0,idxb0)+alpha*v30);
               c.Set(idxa0,idxb0+1,beta*c.Get(idxa0,idxb0+1)+alpha*v31);
               c.Set(idxa0,idxb0+2,beta*c.Get(idxa0,idxb0+2)+alpha*v32);
               c.Set(idxa0,idxb0+3,beta*c.Get(idxa0,idxb0+3)+alpha*v33);
              }
           }
         else
           {
            //--- Determine submatrix [I0..I1]x[J0..J1] to process
            i0=i;
            i1=MathMin(i+3,m-1);
            j0=j;
            j1=MathMin(j+3,n-1);
            //--- Process submatrix
            for(ik=i0; ik<=i1; ik++)
              {
               for(jk=j0; jk<=j1; jk++)
                 {
                  v=0;
                  if(k!=0 && alpha!=0.0)
                    {
                     i1_=(ib)-(ia);
                     for(i_=ia; i_<ia+k; i_++)
                        v+=a.Get(i_,ja+ik)*b.Get(i_+i1_,jb+jk);
                    }
                  if(beta==0.0)
                     c.Set(ic+ik,jc+jk,alpha*v);
                  else
                     c.Set(ic+ik,jc+jk,beta*c.Get(ic+ik,jc+jk)+alpha*v);
                 }
              }
           }   // else
        }   // end for(j)
     }   // end for(i)
  }
//+------------------------------------------------------------------+
//| RMatrixGEMM kernel, basecase code for RMatrixGEMM, specialized   |
//| for sitation with OpTypeA=1 and OpTypeB=1.                       |
//| Additional info:                                                 |
//|   * this function requires that Alpha<>0 (assertion is thrown    |
//|     otherwise)                                                   |
//| INPUT PARAMETERS:                                                |
//|   M        -  matrix size, M>0                                   |
//|   N        -  matrix size, N>0                                   |
//|   K        -  matrix size, K>0                                   |
//|   Alpha    -  coefficient                                        |
//|   A        -  matrix                                             |
//|   IA       -  submatrix offset                                   |
//|   JA       -  submatrix offset                                   |
//|   B        -  matrix                                             |
//|   IB       -  submatrix offset                                   |
//|   JB       -  submatrix offset                                   |
//|   Beta     -  coefficient                                        |
//|   C        -  PREALLOCATED output matrix                         |
//|   IC       -  submatrix offset                                   |
//|   JC       -  submatrix offset                                   |
//+------------------------------------------------------------------+
void CAblasF::RMatrixGemmK44v11(int m,int n,int k,double alpha,
                                const CMatrixDouble &a,int ia,int ja,
                                const CMatrixDouble &b,int ib,int jb,
                                double beta,CMatrixDouble &c,int ic,int jc)
  {
//--- check
   if(!CAp::Assert(alpha!=0.0,__FUNCTION__": internal error (Alpha=0)"))
      return;
//--- if matrix size is zero
   if(m==0 || n==0)
      return;
//--- create variables
   int    i=0;
   int    j=0;
   double v=0;
   double v00=0;
   double v01=0;
   double v02=0;
   double v03=0;
   double v10=0;
   double v11=0;
   double v12=0;
   double v13=0;
   double v20=0;
   double v21=0;
   double v22=0;
   double v23=0;
   double v30=0;
   double v31=0;
   double v32=0;
   double v33=0;
   double a0=0;
   double a1=0;
   double a2=0;
   double a3=0;
   double b0=0;
   double b1=0;
   double b2=0;
   double b3=0;
   int    idxa0=0;
   int    idxa1=0;
   int    idxa2=0;
   int    idxa3=0;
   int    idxb0=0;
   int    idxb1=0;
   int    idxb2=0;
   int    idxb3=0;
   int    i0=0;
   int    i1=0;
   int    ik=0;
   int    j0=0;
   int    j1=0;
   int    jk=0;
   int    t=0;
   int    offsa=0;
   int    offsb=0;
   int    i_=0;
   int    i1_=0;
//--- A'*B'
   for(i=0; i<m; i+=4)
     {
      for(j=0; j<n; j+=4)
        {
         //--- Choose between specialized 4x4 code and general code
         if(i+4<=m && j+4<=n)
           {
            //--- Specialized 4x4 code for [I..I+3]x[J..J+3] submatrix of C.
            //--- This submatrix is calculated as sum of K rank-1 products,
            //--- with operands cached in local variables in order to speed
            //--- up operations with arrays.
            idxa0=ja+i;
            idxa1=idxa0+1;
            idxa2=idxa0+2;
            idxa3=idxa0+3;
            offsa=ia;
            idxb0=ib+j;
            idxb1=idxb0+1;
            idxb2=idxb0+2;
            idxb3=idxb0+3;
            offsb=jb;
            v00=0.0;
            v01=0.0;
            v02=0.0;
            v03=0.0;
            v10=0.0;
            v11=0.0;
            v12=0.0;
            v13=0.0;
            v20=0.0;
            v21=0.0;
            v22=0.0;
            v23=0.0;
            v30=0.0;
            v31=0.0;
            v32=0.0;
            v33=0.0;
            for(t=0; t<k; t++)
              {
               a0=a.Get(offsa,idxa0);
               a1=a.Get(offsa,idxa1);
               b0=b.Get(idxb0,offsb);
               b1=b.Get(idxb1,offsb);
               v00+=a0*b0;
               v01+=a0*b1;
               v10+=a1*b0;
               v11+=a1*b1;
               a2=a.Get(offsa,idxa2);
               a3=a.Get(offsa,idxa3);
               v20+=a2*b0;
               v21+=a2*b1;
               v30+=a3*b0;
               v31+=a3*b1;
               b2=b.Get(idxb2,offsb);
               b3=b.Get(idxb3,offsb);
               v22+=a2*b2;
               v23+=a2*b3;
               v32+=a3*b2;
               v33+=a3*b3;
               v02+=a0*b2;
               v03+=a0*b3;
               v12+=a1*b2;
               v13+=a1*b3;
               offsa++;
               offsb++;
              }
            if(beta==0.0)
              {
               idxa0=ic+i;
               idxb0=jc+j;
               c.Set(idxa0,idxb0,alpha*v00);
               c.Set(idxa0,idxb0+1,alpha*v01);
               c.Set(idxa0,idxb0+2,alpha*v02);
               c.Set(idxa0,idxb0+3,alpha*v03);
               idxa0++;
               c.Set(idxa0,idxb0,alpha*v10);
               c.Set(idxa0,idxb0+1,alpha*v11);
               c.Set(idxa0,idxb0+2,alpha*v12);
               c.Set(idxa0,idxb0+3,alpha*v13);
               idxa0++;
               c.Set(idxa0,idxb0,alpha*v20);
               c.Set(idxa0,idxb0+1,alpha*v21);
               c.Set(idxa0,idxb0+2,alpha*v22);
               c.Set(idxa0,idxb0+3,alpha*v23);
               idxa0++;
               c.Set(idxa0,idxb0,alpha*v30);
               c.Set(idxa0,idxb0+1,alpha*v31);
               c.Set(idxa0,idxb0+2,alpha*v32);
               c.Set(idxa0,idxb0+3,alpha*v33);
              }
            else
              {
               idxa0=ic+i;
               idxb0=jc+j;
               c.Set(idxa0,idxb0,beta*c.Get(idxa0,idxb0)+alpha*v00);
               c.Set(idxa0,idxb0+1,beta*c.Get(idxa0,idxb0+1)+alpha*v01);
               c.Set(idxa0,idxb0+2,beta*c.Get(idxa0,idxb0+2)+alpha*v02);
               c.Set(idxa0,idxb0+3,beta*c.Get(idxa0,idxb0+3)+alpha*v03);
               idxa0++;
               c.Set(idxa0,idxb0,beta*c.Get(idxa0,idxb0)+alpha*v10);
               c.Set(idxa0,idxb0+1,beta*c.Get(idxa0,idxb0+1)+alpha*v11);
               c.Set(idxa0,idxb0+2,beta*c.Get(idxa0,idxb0+2)+alpha*v12);
               c.Set(idxa0,idxb0+3,beta*c.Get(idxa0,idxb0+3)+alpha*v13);
               idxa0++;
               c.Set(idxa0,idxb0,beta*c.Get(idxa0,idxb0)+alpha*v20);
               c.Set(idxa0,idxb0+1,beta*c.Get(idxa0,idxb0+1)+alpha*v21);
               c.Set(idxa0,idxb0+2,beta*c.Get(idxa0,idxb0+2)+alpha*v22);
               c.Set(idxa0,idxb0+3,beta*c.Get(idxa0,idxb0+3)+alpha*v23);
               idxa0++;
               c.Set(idxa0,idxb0,beta*c.Get(idxa0,idxb0)+alpha*v30);
               c.Set(idxa0,idxb0+1,beta*c.Get(idxa0,idxb0+1)+alpha*v31);
               c.Set(idxa0,idxb0+2,beta*c.Get(idxa0,idxb0+2)+alpha*v32);
               c.Set(idxa0,idxb0+3,beta*c.Get(idxa0,idxb0+3)+alpha*v33);
              }
           }
         else
           {
            //--- Determine submatrix [I0..I1]x[J0..J1] to process
            i0=i;
            i1=MathMin(i+3,m-1);
            j0=j;
            j1=MathMin(j+3,n-1);
            //--- Process submatrix
            for(ik=i0; ik<=i1; ik++)
              {
               for(jk=j0; jk<=j1; jk++)
                 {
                  v=0.0;
                  if(k!=0 && alpha!=0.0)
                    {
                     i1_=(jb)-(ia);
                     for(i_=ia; i_<ia+k; i_++)
                        v+=a.Get(i_,ja+ik)*b.Get(ib+jk,i_+i1_);
                    }
                  if(beta==0.0)
                     c.Set(ic+ik,jc+jk,alpha*v);
                  else
                     c.Set(ic+ik,jc+jk,beta*c.Get(ic+ik,jc+jk)+alpha*v);
                 }
              }
           }   // else
        }   // end for(j)
     }   // end for(i)
  }
//+------------------------------------------------------------------+
//| Computes dot product (X,Y) for elements [0,N) of X[] and Y[]     |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   X        -  array[N], vector to process                        |
//|   Y        -  array[N], vector to process                        |
//| RESULT:                                                          |
//|   (X,Y)                                                          |
//+------------------------------------------------------------------+
double CAblasF::RDotV(int n,CRowDouble &x,CRowDouble &y)
  {
//--- check
   if(!CAp::Assert((int)x.Size()>=n,__FUNCTION__": X size less N"))
      return(0);
   if(!CAp::Assert((int)y.Size()>=n,__FUNCTION__": Y size less N"))
      return(0);
//--- Quick exit
   if(n<=0)
      return(0);

   double result=0;

   if(x.Size()==n && y.Size()==n)
      result=x.Dot(y);
   else
      for(int i=0; i<n; i++)
         result+=x[i]*y[i];
//---return result
   return(result);
  }
//+------------------------------------------------------------------+
//| Computes dot product (X,A[i]) for elements [0,N) of vector X[]   |
//| and row A[i,*]                                                   |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   X        -  array[N], vector to process                        |
//|   A        -  array[?,N], matrix to process                      |
//|   I        -  row index                                          |
//| RESULT:                                                          |
//|   (X,Ai)                                                         |
//+------------------------------------------------------------------+
double CAblasF::RDotVR(int n,CRowDouble &x,CMatrixDouble &a,int i)
  {
//--- create variables
   CRowDouble row;
//--- check
   if(!CAp::Assert((int)a.Rows()>=i,__FUNCTION__": A rows less I"))
      return(0);

   row=a[i]+0;
   row.Resize(n);
//---return result
   return(RDotV(n,x,row));
  }
//+------------------------------------------------------------------+
//| Computes dot product (X,A[i]) for elements [0,N) of vector X[]   |
//| and row A[i,*]                                                   |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   X        -  array[N], vector to process                        |
//|   A        -  array[?,N], matrix to process                      |
//|   I        -  colum index                                        |
//| RESULT:                                                          |
//|   (X,Ai)                                                         |
//+------------------------------------------------------------------+
double CAblasF::RDotVC(int n,CRowDouble &x,CMatrixDouble &a,int i)
  {
//--- create variables
   CRowDouble col;
//--- check
   if(!CAp::Assert((int)a.Cols()>=i,__FUNCTION__": A cols less I"))
      return(0);

   col=a.Col(i)+0;
   col.Resize(n);
//---return result
   return(RDotV(n,x,col));
  }
//+------------------------------------------------------------------+
//| Computes dot product(X, A[i]) for rows A[ia, *] and B[ib, *]     |
//| INPUT PARAMETERS :                                               |
//|   N        -  vector length                                      |
//|   A        -  array[ ?, N], matrix to process                    |
//|   IA       -  row index                                          |
//|   B        -  array[ ?, N], matrix to process                    |
//|   IB       -  row index                                          |
//| RESULT :                                                         |
//|   (Ai, Bi)                                                       |
//+------------------------------------------------------------------+
double CAblasF::RDotRR(int n,CMatrixDouble &a,int ia,
                       CMatrixDouble &b,int ib)
  {
//--- check
   if(!CAp::Assert((int)a.Rows()>=ia,__FUNCTION__": A rows less IA"))
      return(0);
   if(!CAp::Assert((int)b.Rows()>=ib,__FUNCTION__": B rows less IB"))
      return(0);
//--- create variables
   CRowDouble A=a[ia]+0;
   CRowDouble B=b[ib]+0;
   A.Resize(n);
   B.Resize(n);
//--- return result
   return(RDotV(n,A,B));
  }
//+------------------------------------------------------------------+
//| Computes dot product (X,X) for elements [0,N) of X[]             |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   X        -  array[N], vector to process                        |
//| RESULT:                                                          |
//|   (X,X)                                                          |
//+------------------------------------------------------------------+
double CAblasF::RDotV2(int n,CRowDouble &x)
  {
   return(RDotV(n,x,x));
  }
//+------------------------------------------------------------------+
//| Performs inplace addition of Y[] to X[]                          |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   Alpha    -  multiplier                                         |
//|   Y        -  array[N], vector to process                        |
//|   X        -  array[N], vector to process                        |
//| RESULT:                                                          |
//|   X := X + alpha*Y                                               |
//+------------------------------------------------------------------+
void CAblasF::RAddV(int n,double alpha,CRowDouble &y,CRowDouble &x)
  {
//--- check
   if(!CAp::Assert((int)x.Size()>=n,__FUNCTION__": X size less N"))
      return;
   if(!CAp::Assert((int)y.Size()>=n,__FUNCTION__": Y size less N"))
      return;
//--- try vector method
   if(x.Size()==n && y.Size()==n)
     {
      x+=y*alpha+0;
      return;
     }
//--- loop
   for(int i=0; i<n; i++)
      x.Add(i,alpha*y[i]);
  }
//+------------------------------------------------------------------+
//| Performs inplace addition of Y[] to X[]                          |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   Alpha    -  multiplier                                         |
//|   Y        -  source vector                                      |
//|   OffsY    -  source offset                                      |
//|   X        -  destination vector                                 |
//|   OffsX    -  destination offset                                 |
//| RESULT:                                                          |
//|   X := X + alpha*Y                                               |
//+------------------------------------------------------------------+
void CAblasF::RAddVX(int n,double alpha,CRowDouble &y,int offsy,
                     CRowDouble &x,int offsx)
  {
//--- check
   if(offsx==0 && offsy==0)
     {
      RAddV(n,alpha,y,x);
      return;
     }
   if(!CAp::Assert((int)x.Size()>=(n+offsx),__FUNCTION__": X size less N+OffsX"))
      return;
   if(!CAp::Assert((int)y.Size()>=(n+offsy),__FUNCTION__": Y size less N+OffsY"))
      return;

   for(int i=0; i<n; i++)
      x.Set(offsx+i,x[offsx+i]+alpha*y[offsy+i]);
  }
//+------------------------------------------------------------------+
//| Performs inplace addition of vector Y[] to row X[]               |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   Alpha    -  multiplier                                         |
//|   Y        -  vector to add                                      |
//|   X        -  target row RowIdx                                  |
//| RESULT:                                                          |
//|   X := X + alpha*Y                                               |
//+------------------------------------------------------------------+
void CAblasF::RAddVR(int n,double alpha,CRowDouble &y,
                     CMatrixDouble &x,int rowidx)
  {
   if(alpha==0)
      return;
//--- check
   if(!CAp::Assert((int)x.Rows()>=rowidx,__FUNCTION__": X rows less RowIdx"))
      return;
   if(!CAp::Assert((int)x.Cols()>=n,__FUNCTION__": X cols less N"))
      return;
   if(!CAp::Assert((int)y.Size()>=n,__FUNCTION__": Y size less N"))
      return;

   if(x.Cols()==n && y.Size()==n)
      x.Row(rowidx,x[rowidx]+y*alpha);
   else
      for(int i=0; i<n; i++)
         x.Set(rowidx,i,x.Get(rowidx,i)+alpha*y[i]);
  }
//+------------------------------------------------------------------+
//| Performs inplace addition of Y[]*Z[] to X[]                      |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   Y        -  array[N], vector to process                        |
//|   Z        -  array[N], vector to process                        |
//|   X        -  array[N], vector to process                        |
//| RESULT:                                                          |
//|   X := X + Y*Z                                                   |
//+------------------------------------------------------------------+
void CAblasF::RMulAddV(int n,CRowDouble &y,CRowDouble &z,CRowDouble &x)
  {
//--- check
   if(!CAp::Assert((int)x.Size()>=n,__FUNCTION__": X size less N"))
      return;
   if(!CAp::Assert((int)y.Size()>=n,__FUNCTION__": Y size less N"))
      return;
   if(!CAp::Assert((int)z.Size()>=n,__FUNCTION__": Z size less N"))
      return;

   if(x.Size()==n && y.Size()==n && z.Size()==n)
      x+=y*z+0;
   else
      for(int i=0; i<n; i++)
         x.Set(i,x[i]+y[i]*z[i]);
  }
//+------------------------------------------------------------------+
//| Performs inplace subtraction of Y[]*Z[] from X[]                 |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   Y        -  array[N], vector to process                        |
//|   Z        -  array[N], vector to process                        |
//|   X        -  array[N], vector to process                        |
//| RESULT:                                                          |
//|   X := X - Y*Z                                                   |
//+------------------------------------------------------------------+
void CAblasF::RNegMulAddV(int n,CRowDouble &y,CRowDouble &z,CRowDouble &x)
  {
//--- check
   if(!CAp::Assert(x.Size()>=n,__FUNCTION__": X size less N"))
      return;
   if(!CAp::Assert(y.Size()>=n,__FUNCTION__": Y size less N"))
      return;
   if(!CAp::Assert(z.Size()>=n,__FUNCTION__": Z size less N"))
      return;

   if(x.Size()==n && y.Size()==n && z.Size()==n)
      x-=y*z+0;
   else
      for(int i=0; i<n; i++)
         x.Set(i,x[i]-y[i]*z[i]);
  }
//+------------------------------------------------------------------+
//| Performs addition of Y[]*Z[] to X[]                              |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   Y        -  array[N], vector to process                        |
//|   Z        -  array[N], vector to process                        |
//|   X        -  array[N], vector to process                        |
//|   R        -  array[N], vector to process                        |
//| RESULT:                                                          |
//|   R := X + Y*Z                                                   |
//+------------------------------------------------------------------+
void CAblasF::RCopyMulAddV(int n,CRowDouble &y,CRowDouble &z,
                           CRowDouble &x,CRowDouble &r)
  {
//--- check
   if(!CAp::Assert(x.Size()>=n,__FUNCTION__": X size less N"))
      return;
   if(!CAp::Assert(y.Size()>=n,__FUNCTION__": Y size less N"))
      return;
   if(!CAp::Assert(z.Size()>=n,__FUNCTION__": Z size less N"))
      return;

   if(x.Size()==n && y.Size()==n && z.Size()==n)
     {
      r=x.ToVector()+y*z;
      return;
     }

   if(r.Size()<n)
      if(!CAp::Assert(z.Resize(n),__FUNCTION__": error resize vector R"))
         return;
   for(int i=0; i<n; i++)
      r.Set(i,x[i]+y[i]*z[i]);
  }
//+------------------------------------------------------------------+
//| Performs subtraction of Y[]*Z[] from X[]                         |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   Y        -  array[N], vector to process                        |
//|   Z        -  array[N], vector to process                        |
//|   X        -  array[N], vector to process                        |
//|   R        -  array[N], vector to process                        |
//| RESULT:                                                          |
//|   R := X - Y * Z                                                 |
//+------------------------------------------------------------------+
void CAblasF::RCopyNegMulAddV(int n,CRowDouble &y,CRowDouble &z,
                              CRowDouble &x,CRowDouble &r)
  {
//--- check
   if(!CAp::Assert(x.Size()>=n,__FUNCTION__": X size less N"))
      return;
   if(!CAp::Assert(y.Size()>=n,__FUNCTION__": Y size less N"))
      return;
   if(!CAp::Assert(z.Size()>=n,__FUNCTION__": Z size less N"))
      return;

   if(x.Size()==n && y.Size()==n && z.Size()==n)
     {
      r=x.ToVector()-y*z;
      return;
     }

   if(r.Size()<n)
      if(!CAp::Assert(z.Resize(n),__FUNCTION__": error resize vector R"))
         return;
   for(int i=0; i<n; i++)
      r.Set(i,x[i]-y[i]*z[i]);
  }
//+------------------------------------------------------------------+
//| Performs componentwise multiplication of vector X[] by vector Y[]|
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   Y        -  vector to multiply by                              |
//|   X        -  target vector                                      |
//| RESULT:                                                          |
//|   X := componentwise(X*Y)                                        |
//+------------------------------------------------------------------+
void CAblasF::RMergeMulV(int n,CRowDouble &y,CRowDouble &x)
  {
//--- check
   if(!CAp::Assert(x.Size()>=n,__FUNCTION__": X size less N"))
      return;
   if(!CAp::Assert(y.Size()>=n,__FUNCTION__": Y size less N"))
      return;

   if(x.Size()==n && y.Size()==n)
      x*=y;
   else
      for(int i=0; i<n; i++)
         x.Set(i,x[i]*y[i]);
  }
//+------------------------------------------------------------------+
//| Performs componentwise multiplication of row X[] by vector Y[]   |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   Y        -  vector to multiply by                              |
//|   X        -  target row RowIdx                                  |
//| RESULT:                                                          |
//|   X := componentwise(X*Y)                                        |
//+------------------------------------------------------------------+
void CAblasF::RMergeMulVR(int n,CRowDouble &y,CMatrixDouble &x,int rowidx)
  {
//--- check
   if(!CAp::Assert(x.Rows()>=rowidx,__FUNCTION__": X Rows less RowIdx"))
      return;
   if(!CAp::Assert(y.Size()>=n,__FUNCTION__": Y size less N"))
      return;
   if(!CAp::Assert(x.Cols()>=n,__FUNCTION__": X Cols less N"))
      return;

   for(int i=0; i<n; i++)
      x.Mul(rowidx,i,y[i]);
  }
//+------------------------------------------------------------------+
//| Performs componentwise multiplication of row X[] by vector Y[]   |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   Y        -  row RowIdY to multiply by                          |
//|   X        -  target vector                                      |
//| RESULT:                                                          |
//|   X := componentwise(X*Y)                                        |
//+------------------------------------------------------------------+
void CAblasF::RMergeMulRV(int n,CMatrixDouble &y,int rowidy,CRowDouble &x)
  {
//--- check
   if(!CAp::Assert(y.Rows()>=rowidy,__FUNCTION__": Y Rows less RowIdx"))
      return;
   if(!CAp::Assert(x.Size()>=n,__FUNCTION__": X size less N"))
      return;
   if(!CAp::Assert(y.Cols()>=n,__FUNCTION__": Y Cols less N"))
      return;

   if(x.Size()==n)
     {
      if(y.Cols()==n)
         x*=y[rowidy]+0;
      else
        {
         vector<double> temp=y[rowidy];
         if(!temp.Resize(n))
            return;
         x*=temp;
        }
     }
   else
      for(int i=0; i<n; i++)
         x.Set(i,x[i]*y.Get(rowidy,i));
  }
//+------------------------------------------------------------------+
//| Performs componentwise division of vector X[] by vector Y[]      |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   Y        -  vector to divide by                                |
//|   X        -  target vector                                      |
//| RESULT:                                                          |
//|   X := componentwise(X/Y)                                        |
//+------------------------------------------------------------------+
void CAblasF::RMergeDivV(int n,CRowDouble &y,CRowDouble &x)
  {
//--- check
   if(!CAp::Assert(x.Size()>=n,__FUNCTION__": X size less N"))
      return;
   if(!CAp::Assert(y.Size()>=n,__FUNCTION__": Y size less N"))
      return;

   if(x.Size()==n && y.Size()==n)
      x/=y;
   else
      for(int i=0; i<n; i++)
         x.Mul(i,MathPow(y[i],-1.0));
  }
//+------------------------------------------------------------------+
//| Performs componentwise division of row X[] by vector Y[]         |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   Y       -   vector to divide by                                |
//|   X       -   target row RowIdx                                  |
//| RESULT:                                                          |
//|   X := componentwise(X/Y)                                        |
//+------------------------------------------------------------------+
void CAblasF::RMergeDivVR(int n,CRowDouble &y,CMatrixDouble &x,int rowidx)
  {
//--- check
   if(!CAp::Assert(x.Rows()>=rowidx,__FUNCTION__": X Rows less RowIdx"))
      return;
   if(!CAp::Assert(y.Size()>=n,__FUNCTION__": Y size less N"))
      return;
   if(!CAp::Assert(x.Cols()>=n,__FUNCTION__": X Cols less N"))
      return;

   for(int i=0; i<n; i++)
      x.Set(rowidx,i,x.Get(rowidx,i)/y[i]);
  }
//+------------------------------------------------------------------+
//| Performs componentwise division of row X[] by vector Y[]         |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   Y        -  vector to divide by                                |
//|   X        -  target row RowIdx                                  |
//| RESULT:                                                          |
//|   X := componentwise(X/Y)                                        |
//+------------------------------------------------------------------+
void CAblasF::RMergeDivRV(int n,CMatrixDouble &y,int rowidy,CRowDouble &x)
  {
//--- check
   if(!CAp::Assert(y.Rows()>=rowidy,__FUNCTION__": Y Rows less RowIdY"))
      return;
   if(!CAp::Assert(x.Size()>=n,__FUNCTION__": X size less N"))
      return;
   if(!CAp::Assert(y.Cols()>=n,__FUNCTION__": Y Cols less N"))
      return;

   if(x.Size()==n)
     {
      if(y.Cols()==n)
         x/=y[rowidy]+0;
      else
        {
         vector<double> temp=y[rowidy];
         if(!temp.Resize(n))
            return;
         x/=temp;
        }
     }
   else
      for(int i=0; i<n; i++)
         x.Set(i,x[i]/y.Get(rowidy,i));
  }
//+------------------------------------------------------------------+
//| Performs componentwise max of vector X[] and vector Y[]          |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   Y        -  vector to multiply by                              |
//|   X        -  target vector                                      |
//| RESULT:                                                          |
//|   X := componentwise_max(X,Y)                                    |
//+------------------------------------------------------------------+
void CAblasF::RMergeMaxV(int n,CRowDouble &y,CRowDouble &x)
  {
//--- check
   if(!CAp::Assert(x.Size()>=n,__FUNCTION__": X size less N"))
      return;
   if(!CAp::Assert(y.Size()>=n,__FUNCTION__": Y size less N"))
      return;

   for(int i=0; i<n; i++)
      x.Set(i,MathMax(x[i],y[i]));
  }
//+------------------------------------------------------------------+
//| Performs componentwise max of row X[] and vector Y[]             |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   Y        -  vector to multiply by                              |
//|   X        -  target row RowIdx                                  |
//| RESULT:                                                          |
//|   X := componentwise_max(X,Y)                                    |
//+------------------------------------------------------------------+
void CAblasF::RMergeMaxVR(int n,CRowDouble &y,CMatrixDouble &x,int rowidx)
  {
//--- check
   if(!CAp::Assert(x.Rows()>=rowidx,__FUNCTION__": X Rows less RowIdx"))
      return;
   if(!CAp::Assert(y.Size()>=n,__FUNCTION__": Y size less N"))
      return;
   if(!CAp::Assert(x.Cols()>=n,__FUNCTION__": X Cols less N"))
      return;

   for(int i=0; i<n; i++)
      x.Set(rowidx,i,MathMax(x.Get(rowidx,i),y[i]));
  }
//+------------------------------------------------------------------+
//| Performs componentwise max of row X[I] and vector Y[]            |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   X        -  matrix, I-th row is source                         |
//|   Y        -  target vector                                      |
//| RESULT:                                                          |
//|   Y := componentwise_max(Y,X)                                    |
//+------------------------------------------------------------------+
void CAblasF::RMergeMaxRV(int n,CMatrixDouble &x,int rowidx,CRowDouble &y)
  {
   if(n<=0)
      return;
//--- check
   if(!CAp::Assert(MathMax(x.Rows(),0)>=rowidx,__FUNCTION__": X Rows less RowIdx"))
      return;
   if(!CAp::Assert(MathMax(y.Size(),0)>=n,__FUNCTION__": Y size less N"))
      return;
   if(!CAp::Assert(MathMax(x.Cols(),0)>=n,__FUNCTION__": X Cols less N"))
      return;

   for(int i=0; i<n; i++)
      y.Set(i,MathMax(y[i],x.Get(rowidx,i)));
  }
//+------------------------------------------------------------------+
//| Performs componentwise min of vector X[] and vector Y[]          |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   Y        -  vector to multiply by                              |
//|   X        -  target vector                                      |
//| RESULT:                                                          |
//|   X := componentwise_max(X,Y)                                    |
//+------------------------------------------------------------------+
void CAblasF::RMergeMinV(int n,CRowDouble &y,CRowDouble &x)
  {
//--- check
   if(!CAp::Assert(x.Size()>=n,__FUNCTION__": X size less N"))
      return;
   if(!CAp::Assert(y.Size()>=n,__FUNCTION__": Y size less N"))
      return;

   for(int i=0; i<n; i++)
      x.Set(i,MathMin(x[i],y[i]));
  }
//+------------------------------------------------------------------+
//| Performs componentwise max of row X[] and vector Y[]             |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   Y        -  vector to multiply by                              |
//|   X        -  target row RowIdx                                  |
//| RESULT:                                                          |
//|   X := componentwise_max(X,Y)                                    |
//+------------------------------------------------------------------+
void CAblasF::RMergeMinVR(int n,CRowDouble &y,CMatrixDouble &x,int rowidx)
  {
//--- check
   if(!CAp::Assert(x.Rows()>=rowidx,__FUNCTION__": X Rows less RowIdx"))
      return;
   if(!CAp::Assert(y.Size()>=n,__FUNCTION__": Y size less N"))
      return;
   if(!CAp::Assert(x.Cols()>=n,__FUNCTION__": X Cols less N"))
      return;

   for(int i=0; i<n; i++)
      x.Set(rowidx,i,MathMin(x.Get(rowidx,i),y[i]));
  }
//+------------------------------------------------------------------+
//| Performs componentwise max of row X[I] and vector Y[]            |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   X        -  matrix, I-th row is source                         |
//|   Y       -   target vector                                      |
//| RESULT:                                                          |
//|   Y := componentwise_max(X,Y)                                    |
//+------------------------------------------------------------------+
void CAblasF::RMergeMinRV(int n,CMatrixDouble &x,int rowidx,CRowDouble &y)
  {
//--- check
   if(!CAp::Assert(x.Rows()>=rowidx,__FUNCTION__": X Rows less RowIdx"))
      return;
   if(!CAp::Assert(y.Size()>=n,__FUNCTION__": Y size less N"))
      return;
   if(!CAp::Assert(x.Cols()>=n,__FUNCTION__": X Cols less N"))
      return;

   for(int i=0; i<n; i++)
      y.Set(i,MathMin(y[i],x.Get(rowidx,i)));
  }
//+------------------------------------------------------------------+
//| Performs inplace addition of Y[RIdx,...] to X[]                  |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   Alpha    -  multiplier                                         |
//|   Y        -  array[?,N], matrix whose RIdx-th row is added      |
//|   RIdx     -  row index                                          |
//|   X        -  array[N], vector to process                        |
//| RESULT:                                                          |
//|   X := X + alpha*Y                                               |
//+------------------------------------------------------------------+
void CAblasF::RAddRV(int n,double alpha,CMatrixDouble &y,int ridx,
                     CRowDouble &x)
  {
//--- check
   if(!CAp::Assert((int)y.Rows()>=ridx,__FUNCTION__": Y Rows less RIdx"))
      return;
   if(!CAp::Assert((int)x.Size()>=n,__FUNCTION__": X size less N"))
      return;
   if(!CAp::Assert((int)y.Cols()>=n,__FUNCTION__": Y Cols less N"))
      return;

   if(x.Size()==n)
     {
      if(y.Cols()==n)
         x+=y[ridx]*alpha;
      else
        {
         vector<double> temp=y[ridx];
         if(!temp.Resize(n))
            return;
         x+=temp*alpha;
        }
     }
   else
      for(int i=0; i<n; i++)
         x.Set(i,x[i]+alpha*y.Get(ridx,i));
  }
//+------------------------------------------------------------------+
//| Performs inplace addition of Y[RIdx,...] to X[RIdxDst]           |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   Alpha    -  multiplier                                         |
//|   Y        -  array[?,N], matrix whose RIdxSrc-th row is added   |
//|   RIdxSrc  -  source row index                                   |
//|   X        -  array[?,N], matrix whose RIdxDst-th row is target  |
//|   RIdxDst  -  destination row index                              |
//| RESULT:                                                          |
//|   X := X + alpha*Y                                               |
//+------------------------------------------------------------------+
void CAblasF::RAddRR(int n,double alpha,CMatrixDouble &y,int ridxsrc,
                     CMatrixDouble &x,int ridxdst)
  {
//--- check
   if(!CAp::Assert((int)x.Rows()>=ridxdst,__FUNCTION__": X Rows less RIdxDst"))
      return;
   if(!CAp::Assert((int)y.Rows()>=ridxsrc,__FUNCTION__": Y Rows less RIdxSrc"))
      return;
   if(!CAp::Assert((int)y.Cols()>=n,__FUNCTION__": Y Cols less N"))
      return;
   if(!CAp::Assert((int)x.Cols()>=n,__FUNCTION__": X Cols less N"))
      return;

   for(int i=0; i<n; i++)
      x.Set(ridxdst,i,x.Get(ridxdst,i)+alpha*y.Get(ridxsrc,i));
  }
//+------------------------------------------------------------------+
//| Performs inplace multiplication of X[] by V                      |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   X        -  array[N], vector to process                        |
//|   V        -  multiplier                                         |
//| OUTPUT PARAMETERS:                                               |
//|   X        -  elements 0...N - 1 multiplied by V                 |
//+------------------------------------------------------------------+
void CAblasF::RMulV(int n,double v,CRowDouble &x)
  {
//--- check
   if(!CAp::Assert(x.Size()>=n,__FUNCTION__": X size less N"))
      return;

   if(x.Size()==n)
      x*=v;
   else
      for(int i=0; i<n; i++)
         x.Mul(i,v);
  }
//+------------------------------------------------------------------+
//| Performs inplace multiplication of X[] by V                      |
//| INPUT PARAMETERS:                                                |
//|   N        -  row length                                         |
//|   X        -  array[?,N], row to process                         |
//|   V        -  multiplier                                         |
//| OUTPUT PARAMETERS:                                               |
//|   X        -  elements 0...N-1 of row RowIdx are multiplied by V |
//+------------------------------------------------------------------+
void CAblasF::RMulR(int n,double v,CMatrixDouble &x,int rowidx)
  {
//--- check
   if(!CAp::Assert(x.Rows()>=rowidx,__FUNCTION__": X Rows less RowIdx"))
      return;
   if(!CAp::Assert(x.Cols()>=n,__FUNCTION__": X Cols less N"))
      return;

   for(int i=0; i<n; i++)
      x.Set(rowidx,i,x.Get(rowidx,i)*v);
  }
//+------------------------------------------------------------------+
//| Performs inplace computation of Sqrt(X)                          |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   X        -  array[N], vector to process                        |
//| OUTPUT PARAMETERS:                                               |
//|   X        -  elements 0...N-1 replaced by Sqrt(X)               |
//+------------------------------------------------------------------+
void CAblasF::RSqrtV(int n,CRowDouble &x)
  {
//--- check
   if(!CAp::Assert(x.Size()>=n,__FUNCTION__": X size less N"))
      return;

   if(x.Size()==n)
      x=x.Sqrt()+0;
   else
      for(int i=0; i<n; i++)
         x.Set(i,MathSqrt(x[i]));
  }
//+------------------------------------------------------------------+
//| Performs inplace computation of Sqrt(X[RowIdx,*])                |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   X        -  array[?,N], matrix to process                      |
//| OUTPUT PARAMETERS:                                               |
//|   X        -  elements 0...N-1 replaced by Sqrt(X)               |
//+------------------------------------------------------------------+
void CAblasF::RSqrtR(int n,CMatrixDouble &x,int rowidx)
  {
//--- check
   if(!CAp::Assert(x.Rows()>=rowidx,__FUNCTION__": X Rows less RowIdx"))
      return;
   if(!CAp::Assert(x.Cols()>=n,__FUNCTION__": X Cols less N"))
      return;

   for(int i=0; i<n; i++)
      x.Set(rowidx,i,MathSqrt(x.Get(rowidx,i)));
  }
//+------------------------------------------------------------------+
//| Performs inplace multiplication of X[OffsX:OffsX+N-1] by V       |
//| INPUT PARAMETERS:                                                |
//|   N        -  subvector length                                   |
//|   X        -  vector to process                                  |
//|   V        -  multiplier                                         |
//| OUTPUT PARAMETERS:                                               |
//|   X        -  elements OffsX:OffsX+N-1 multiplied by V           |
//+------------------------------------------------------------------+
void CAblasF::RMulVX(int n,double v,CRowDouble &x,int offsx)
  {
//--- check
   if(offsx==0)
     {
      RMulV(n,v,x);
      return;
     }
   if(!CAp::Assert(x.Size()>=(n+offsx),__FUNCTION__": X size less N+OffsX"))
      return;

   for(int i=0; i<n; i++)
      x.Mul(offsx+i,v);
  }
//+------------------------------------------------------------------+
//| Returns maximum X                                                |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   X        -  array[N], vector to process                        |
//| OUTPUT PARAMETERS:                                               |
//|   max(X[i])                                                      |
//|   zero for N=0                                                   |
//+------------------------------------------------------------------+
double CAblasF::RMaxV(int n,CRowDouble &x)
  {
//--- check
   if(!CAp::Assert(x.Size()>=n,__FUNCTION__": X size less N"))
      return(0);

   if(n==0)
      return(0);
   if(x.Size()==n)
      return(x.Max());

   vector<double> temp=x.ToVector();
   if(!temp.Resize(n))
      return(0);
//--- return result
   return(temp.Max());
  }
//+------------------------------------------------------------------+
//| Returns maximum |X|                                              |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   X        -  array[N], vector to process                        |
//| OUTPUT PARAMETERS:                                               |
//|   max(|X[i]|)                                                    |
//|  zero for N=0                                                    |
//+------------------------------------------------------------------+
double CAblasF::RMaxAbsV(int n,CRowDouble &x)
  {
//--- check
   if(!CAp::Assert(x.Size()>=n,__FUNCTION__": X size less N"))
      return(0);

   if(n==0)
      return(0);
   if(x.Size()==n)
      return(x.MaxAbs());

   CRowDouble temp=x;
   if(!temp.Resize(n))
      return(0);
//--- return result
   return(temp.MaxAbs());
  }
//+------------------------------------------------------------------+
//| Returns maximum X                                                |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   X        -  matrix to process, RowIdx-th row is processed      |
//| OUTPUT PARAMETERS:                                               |
//|   max(X[RowIdx,i])                                               |
//|   zero for N=0                                                   |
//+------------------------------------------------------------------+
double CAblasF::RMaxR(int n,CMatrixDouble &x,int rowidx)
  {
//--- check
   if(!CAp::Assert(x.Rows()>=rowidx,__FUNCTION__": X Rows less RowIdx"))
      return(0);
   if(!CAp::Assert(x.Cols()>=n,__FUNCTION__": X Cols less N"))
      return(0);

   CRowDouble temp=x[rowidx]+0;
   if(temp.Size()!=n)
      if(!temp.Resize(n))
         return(0);
   return(temp.Max());
  }
//+------------------------------------------------------------------+
//| Returns maximum |X|                                              |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   X        -  matrix to process, RowIdx-th row is processed      |
//| OUTPUT PARAMETERS:                                               |
//|   max(|X[RowIdx,i]|)                                             |
//|   zero for N=0                                                   |
//+------------------------------------------------------------------+
double CAblasF::RMaxAbsR(int n,CMatrixDouble &x,int rowidx)
  {
//--- check
   if(!CAp::Assert(x.Rows()>=rowidx,__FUNCTION__": X Rows less RowIdx"))
      return(0);
   if(!CAp::Assert(x.Cols()>=n,__FUNCTION__": X Cols less N"))
      return(0);

   CRowDouble temp=x[rowidx]+0;
   if(temp.Size()!=n)
      if(!temp.Resize(n))
         return(0);
//--- return result
   return(temp.MaxAbs());
  }
//+------------------------------------------------------------------+
//| Sets vector X[] to V                                             |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   V        -  value to set                                       |
//|   X        -  array[N]                                           |
//| OUTPUT PARAMETERS:                                               |
//|   X        -  leading N elements are replaced by V               |
//+------------------------------------------------------------------+
void CAblasF::RSetV(int n,double v,CRowDouble &x)
  {
//--- check
   if(!CAp::Assert(x.Size()>=n,__FUNCTION__": X size less N"))
      return;

   if(x.Size()==n)
      x.Fill(v);
   else
      for(int j=0; j<n; j++)
         x.Set(j,v);
  }
//+------------------------------------------------------------------+
//| Sets X[OffsX:OffsX+N-1] to V                                     |
//| INPUT PARAMETERS:                                                |
//|   N        -  subvector length                                   |
//|   V        -  value to set                                       |
//|   X        -  array[N]                                           |
//| OUTPUT PARAMETERS:                                               |
//|   X        -  X[OffsX:OffsX+N-1] is replaced by V                |
//+------------------------------------------------------------------+
void CAblasF::RSetVX(int n,double v,CRowDouble &x,int offsx)
  {
//--- check
   if(offsx==0)
     {
      RSetV(n,v,x);
      return;
     }
   if(!CAp::Assert(x.Size()>=n,__FUNCTION__": X size less N"))
      return;

   for(int j=0; j<n; j++)
      x.Set(offsx+j,v);
  }
//+------------------------------------------------------------------+
//| Sets vector X[] to V                                             |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   V        -  value to set                                       |
//|   X        -  array[N]                                           |
//| OUTPUT PARAMETERS:                                               |
//|   X        -  leading N elements are replaced by V               |
//+------------------------------------------------------------------+
void CAblasF::ISetV(int n,int v,CRowInt &x)
  {
   x.Fill(v,0,n);
  }
//+------------------------------------------------------------------+
//| Sets vector X[] to V                                             |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   V        -  value to set                                       |
//|   X        -  array[N]                                           |
//| OUTPUT PARAMETERS:                                               |
//|   X        -  leading N elements are replaced by V               |
//+------------------------------------------------------------------+
void CAblasF::BSetV(int n,bool v,bool &x[])
  {
   ArrayFill(x,0,n,v);
  }
//+------------------------------------------------------------------+
//| Sets matrix A[] to V                                             |
//| INPUT PARAMETERS:                                                |
//|   M, N     -  rows/cols count                                    |
//|   V        -  value to set                                       |
//|   A        -  array[M,N]                                         |
//| OUTPUT PARAMETERS:                                               |
//|   A        -  leading M rows, N cols are replaced by V           |
//+------------------------------------------------------------------+
void CAblasF::RSetM(int m,int n,double v,CMatrixDouble &a)
  {
//--- check
   if(!CAp::Assert(a.Rows()>=m,__FUNCTION__": A Rows less M"))
      return;
   if(!CAp::Assert(a.Cols()>=n,__FUNCTION__": A Cols less N"))
      return;
   a.Fill(v,m,n);
  }
//+------------------------------------------------------------------+
//| Sets row I of A[,] to V                                          |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   V        -  value to set                                       |
//|   A        -  array[N,N] or larger                               |
//|   I        -  row index                                          |
//| OUTPUT PARAMETERS:                                               |
//|   A        -  leading N elements of I-th row are replaced by V   |
//+------------------------------------------------------------------+
void CAblasF::RSetR(int n,double v,CMatrixDouble &a,int i)
  {
//--- check
   if(!CAp::Assert(a.Rows()>=i,__FUNCTION__": A Rows less I"))
      return;
   if(!CAp::Assert(a.Cols()>=n,__FUNCTION__": A Cols less N"))
      return;

   if(a.Cols()==n)
      a.Row(i,vector<double>::Full(n,v));
   else
      for(int j=0; j<n; j++)
         a.Set(i,j,v);
  }
//+------------------------------------------------------------------+
//| Copies vector X[] to Y[]                                         |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   X        -  array[N], source                                   |
//|   Y        -  preallocated array[N]                              |
//| OUTPUT PARAMETERS:                                               |
//|   Y        -  leading N elements are replaced by X               |
//| NOTE: destination and source should NOT overlap                  |
//+------------------------------------------------------------------+
void CAblasF::RCopyV(int n,CRowDouble &x,CRowDouble &y)
  {
//--- check
   if(!CAp::Assert(x.Size()>=n,__FUNCTION__": X size less N"))
      return;

   if(y.Size()<=n)
     {
      y=x;
      if(y.Size()>n)
         y.Resize(n);
     }
   else
      for(int j=0; j<n; j++)
         y.Set(j,x[j]);
  }
//+------------------------------------------------------------------+
//| Copies vector X[] to Y[]                                         |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   X        -  array[N], source                                   |
//|   Y        -  preallocated array[N]                              |
//| OUTPUT PARAMETERS:                                               |
//|   Y        -  leading N elements are replaced by X               |
//| NOTE: destination and source should NOT overlap                  |
//+------------------------------------------------------------------+
void CAblasF::BCopyV(int n,bool &x[],bool &y[])
  {
   for(int j=0; j<n; j++)
      y[j]=x[j];
  }
//+------------------------------------------------------------------+
//| Copies vector X[] to Y[]                                         |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   X        -  source array                                       |
//|   Y        -  preallocated array[N]                              |
//| OUTPUT PARAMETERS:                                               |
//|   Y        -  X copied to Y                                      |
//+------------------------------------------------------------------+
void CAblasF::ICopyV(int n,CRowInt &x,CRowInt &y)
  {
//--- check
   if(!CAp::Assert(x.Size()>=n,__FUNCTION__": X size less N"))
      return;

   y.Copy(x,0,0,n);
  }
//+------------------------------------------------------------------+
//| Performs copying with multiplication of V*X[] to Y[]             |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   V        -  multiplier                                         |
//|   X        -  array[N], source                                   |
//|   Y        -  preallocated array[N]                              |
//| OUTPUT PARAMETERS:                                               |
//|   Y        -  array[N], Y = V*X                                  |
//+------------------------------------------------------------------+
void CAblasF::RCopyMulV(int n,double v,CRowDouble &x,CRowDouble &y)
  {
//--- check
   if(!CAp::Assert(x.Size()>=n,__FUNCTION__": X size less N"))
      return;

   if(y.Size()<=n)
     {
      y=x*v+0;
      if(y.Size()>n)
         y.Resize(n);
      return;
     }

   for(int i=0; i<n; i++)
      y.Set(i,v*x[i]);
  }
//+------------------------------------------------------------------+
//| Performs copying with multiplication of V*X[] to Y[I,*]          |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   V        -  multiplier                                         |
//|   X        -  array[N], source                                   |
//|   Y        -  preallocated array[?,N]                            |
//|   RIdx     -  destination row index                              |
//| OUTPUT PARAMETERS:                                               |
//|   Y        -  Y[RIdx,...] = V*X                                  |
//+------------------------------------------------------------------+
void CAblasF::RCopyMulVR(int n,double v,CRowDouble &x,
                         CMatrixDouble &y,int ridx)
  {
//--- check
   if(!CAp::Assert(y.Rows()>=ridx,__FUNCTION__": y Rows less RIdx"))
      return;
   if(!CAp::Assert(y.Cols()>=n,__FUNCTION__": Y Cols less N"))
      return;
   if(!CAp::Assert(x.Size()>=n,__FUNCTION__": X size less N"))
      return;

   for(int i=0; i<n; i++)
      y.Set(ridx,i,v*x[i]);
  }
//+------------------------------------------------------------------+
//| Copies vector X[] to row I of A[,]                               |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   X        -  array[N], source                                   |
//|   A        -  preallocated 2D array large enough to store result |
//|   I        -  destination row index                              |
//| OUTPUT PARAMETERS:                                               |
//|   A        -  leading N elements of I-th row are replaced by X   |
//+------------------------------------------------------------------+
void CAblasF::RCopyVR(int n,CRowDouble &x,CMatrixDouble &a,int i)
  {
//--- check
   if(!CAp::Assert(a.Rows()>=i,__FUNCTION__": A Rows less I"))
      return;
   if(!CAp::Assert(a.Cols()>=n,__FUNCTION__": A Cols less N"))
      return;
   if(!CAp::Assert(x.Size()>=n,__FUNCTION__": X size less N"))
      return;

   if(a.Cols()==n && x.Size()==n)
      a.Row(i,x);
   else
      for(int j=0; j<n; j++)
         a.Set(i,j,x[j]);
  }
//+------------------------------------------------------------------+
//| Copies row I of A[,] to vector X[]                               |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   A        -  2D array, source                                   |
//|   I        -  source row index                                   |
//|   X        -  preallocated destination                           |
//| OUTPUT PARAMETERS:                                               |
//|   X        -  array[N], destination                              |
//+------------------------------------------------------------------+
void CAblasF::RCopyRV(int n,CMatrixDouble &a,int i,CRowDouble &x)
  {
//--- check
   if(!CAp::Assert(a.Rows()>=i,__FUNCTION__": A Rows less I"))
      return;

   CRowDouble temp=a[i]+0;
   RCopyV(n,temp,x);
  }
//+------------------------------------------------------------------+
//| Copies row I of A[,] to row K of B[,].                           |
//| A[i,...] and B[k,...] may overlap.                               |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   A        -  2D array, source                                   |
//|   I        -  source row index                                   |
//|   B        -  preallocated destination                           |
//|   K        -  destination row index                              |
//| OUTPUT PARAMETERS:                                               |
//|   B        -  row K overwritten                                  |
//+------------------------------------------------------------------+
void CAblasF::RCopyRR(int n,CMatrixDouble &a,int i,CMatrixDouble &b,int k)
  {
//--- check
   if(!CAp::Assert(a.Rows()>=i,__FUNCTION__": A Rows less i"))
      return;
   if(!CAp::Assert(a.Cols()>=n,__FUNCTION__": A Cols less N"))
      return;
   if(!CAp::Assert(b.Rows()>=k,__FUNCTION__": B Rows less K"))
      return;
   if(!CAp::Assert(b.Cols()>=n,__FUNCTION__": B Cols less N"))
      return;

   if(a.Cols()==n && b.Cols()==n)
      b.Row(k,a[i]+0);
   else
      for(int j=0; j<n; j++)
         b.Set(k,j,a.Get(i,j));
  }
//+------------------------------------------------------------------+
//| Copies vector X[] to Y[], extended version                       |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   X        -  source array                                       |
//|   OffsX    -  source offset                                      |
//|   Y        -  preallocated array[N]                              |
//|   OffsY    -  destination offset                                 |
//| OUTPUT PARAMETERS:                                               |
//|   Y        -  N elements starting from OffsY are replaced        |
//|               by X[OffsX:OffsX+N-1]                              |
//| NOTE: destination and source should NOT overlap                  |
//+------------------------------------------------------------------+
void CAblasF::RCopyVX(int n,CRowDouble &x,int offsx,
                      CRowDouble &y,int offsy)
  {
//--- check
   if(!CAp::Assert(x.Size()>=(n+offsx),__FUNCTION__": X size less N+OffsX"))
      return;
   if(!CAp::Assert(y.Size()>=(n+offsy),__FUNCTION__": Y size less N+OffsY"))
      return;

   for(int j=0; j<n; j++)
      y.Set(offsy+j,x[offsx+j]);
  }
//+------------------------------------------------------------------+
//| Copies vector X[] to Y[], extended version                       |
//| INPUT PARAMETERS:                                                |
//|   N        -  vector length                                      |
//|   X        -  source array                                       |
//|   OffsX    -  source offset                                      |
//|   Y        -  preallocated array[N]                              |
//|   OffsY    -  destination offset                                 |
//| OUTPUT PARAMETERS:                                               |
//|   Y        -  N elements starting from OffsY are replaced        |
//|               by X[OffsX:OffsX+N-1]                              |
//| NOTE: destination and source should NOT overlap                  |
//+------------------------------------------------------------------+
void CAblasF::ICopyVX(int n,CRowInt &x,int offsx,CRowInt &y,int offsy)
  {
   y.Copy(x,offsy,offsx,n);
  }
//+------------------------------------------------------------------+
//| Matrix-vector product: y := alpha*op(A)*x + beta*y               |
//| NOTE: this function expects Y to be large enough to store result.|
//|       No automatic preallocation happens for smaller arrays. No  |
//|       integrity checks is performed for sizes of A, x, y.        |
//| INPUT PARAMETERS:                                                |
//|   M        -  number of rows of op(A)                            |
//|   N        -  number of columns of op(A)                         |
//|   Alpha    -  coefficient                                        |
//|   A        -  source matrix                                      |
//|   OpA      -  operation type:                                    |
//|               * OpA=0     =>  op(A) = A                          |
//|               * OpA=1     =>  op(A) = A^T                        |
//|   X        -  input vector, has at least N elements              |
//|   Beta     -  coefficient                                        |
//|   Y        -  preallocated output array, has at least M elements |
//| OUTPUT PARAMETERS:                                               |
//|   Y        -  vector which stores result                         |
//| HANDLING OF SPECIAL CASES:                                       |
//|   * if M=0, then subroutine does nothing. It does not even touch |
//|     arrays.                                                      |
//|   * if N=0 or Alpha=0.0, then:                                   |
//|      * if Beta=0, then Y is filled by zeros. A and X are  not    |
//|        referenced at all. Initial values of Y are ignored (we    |
//|        do not multiply Y by zero, we just rewrite it by zeros)   |
//|      * if Beta<>0, then Y is replaced by Beta*Y                  |
//|   * if M>0, N>0, Alpha<>0, but Beta=0, then Y is replaced by     |
//|     A*x; initial state of Y is ignored (rewritten by  A*x,       |
//|     without initial multiplication by zeros).                    |
//+------------------------------------------------------------------+
void CAblasF::RGemV(int m,int n,double alpha,CMatrixDouble &a,int opa,
                    CRowDouble &x,double beta,CRowDouble &y)
  {
//--- create variables
   int    i=0;
   int    j=0;
   double v=0;
//--- Properly premultiply Y by Beta.
//--- Quick exit for M=0, N=0 or Alpha=0.
//--- After this block we have M>0, N>0, Alpha<>0.
   if(m<=0)
      return;
   if(beta!=0.0)
      RMulV(m,beta,y);
   else
      RSetV(m,0.0,y);
   if(n<=0 || alpha==0.0)
      return;
//--- Generic code
   switch(opa)
     {
      case 0:
         //--- y += A*x
         for(i=0; i<m ; i++)
           {
            v=RDotVR(n,x,a,i);
            y.Set(i,alpha*v+y[i]);
           }
         break;
      case 1:
         //--- y += A^T*x
         for(i=0; i<n; i++)
           {
            v=alpha*x[i];
            for(j=0; j<m; j++)
               y.Add(j,v*a.Get(i,j));
           }
         break;
     }
  }
//+------------------------------------------------------------------+
//| Matrix-vector product: y := alpha*op(A)*x + beta*y               |
//| Here  x,  y,  A  are  subvectors/submatrices  of  larger         |
//| vectors/matrices.                                                |
//| NOTE: this function expects Y to be large enough to store result.|
//|       No automatic preallocation happens for  smaller  arrays.   |
//|       No integrity checks is performed for sizes of A, x, y.     |
//| INPUT PARAMETERS:                                                |
//|   M        -  number of rows of op(A)                            |
//|   N        -  number of columns of op(A)                         |
//|   Alpha    -  coefficient                                        |
//|   A        -  source matrix                                      |
//|   IA       -  submatrix offset (row index)                       |
//|   JA       -  submatrix offset (column index)                    |
//|   OpA      -  operation type:                                    |
//|               * OpA=0     =>  op(A) = A                          |
//|               * OpA=1     =>  op(A) = A^T                        |
//|   X        -  input vector, has at least N+IX elements           |
//|   IX       -  subvector offset                                   |
//|   Beta     -  coefficient                                        |
//|   Y        -  preallocated output array, has at least M+IY       |
//|               elements                                           |
//|   IY       -  subvector offset                                   |
//| OUTPUT PARAMETERS:                                               |
//|   Y        -  vector which stores result                         |
//| HANDLING OF SPECIAL CASES:                                       |
//|   * if M=0, then subroutine does nothing. It does not even       |
//|     touch arrays.                                                |
//|   * if N=0 or Alpha=0.0, then:                                   |
//|      * if Beta=0, then Y is filled by zeros. A and X are  not    |
//|        referenced at all. Initial values of Y are ignored (we    |
//|        do not  multiply Y by zero, we just rewrite it by zeros)  |
//|      * if Beta<>0, then Y is replaced by Beta*Y                  |
//|   * if M>0, N>0, Alpha<>0, but Beta=0, then Y is replaced by A*x;|
//|     initial state of Y is ignored (rewritten by A*x, without     |
//|     initial multiplication by zeros).                            |
//+------------------------------------------------------------------+
void CAblasF::RGemVX(int m,int n,double alpha,CMatrixDouble &a,
                     int ia,int ja,int opa,CRowDouble &x,
                     int ix,double beta,CRowDouble &y,int iy)
  {
//--- create variables
   int    i=0;
   int    j=0;
   double v=0;
//--- Properly premultiply Y by Beta.
//--- Quick exit for M=0, N=0 or Alpha=0.
//--- After this block we have M>0, N>0, Alpha<>0.
   if(m<=0)
      return;
   if(beta!=0.0)
      RMulVX(m,beta,y,iy);
   else
      RSetVX(m,0.0,y,iy);
   if(n<=0 || alpha==0.0)
      return;
//--- Generic code
   switch(opa)
     {
      case 0: // y += A*x
         for(i=0; i<m; i++)
           {
            v=0;
            for(j=0; j<n; j++)
               v=v+a.Get(ia+i,ja+j)*x[ix+j];
            y.Add(iy+i,alpha*v);
           }
         break;
      case 1: // y += A^T*x
         for(i=0; i<n; i++)
           {
            v=alpha*x[ix+i];
            for(j=0; j<m; j++)
               y.Add(iy+j,v*a.Get(ia+i,ja+j));
           }
         break;
     }
  }
//+------------------------------------------------------------------+
//| Rank-1 correction: A := A + alpha*u*v'                           |
//| NOTE: this function expects A to be large enough to store result.|
//|       No automatic preallocation happens for smaller arrays. No  |
//|       integrity checks is performed for sizes of A, u, v.        |
//| INPUT PARAMETERS:                                                |
//|   M        -  number of rows                                     |
//|   N        -  number of columns                                  |
//|   A        -  target MxN matrix                                  |
//|   Alpha    -  coefficient                                        |
//|   U        -  vector #1                                          |
//|   V        -  vector #2                                          |
//+------------------------------------------------------------------+
void CAblasF::RGer(int m,int n,double alpha,CRowDouble &u,
                   CRowDouble &v,CMatrixDouble &a)
  {
//--- check
   if(m<=0 || n<=0 || alpha==0.0)
      return;

   for(int i=0; i<m; i++)
     {
      double s=alpha*u[i];
      for(int j=0; j<n; j++)
         a.Set(i,j,a.Get(i,j)+s*v[j]);
     }
  }
//+------------------------------------------------------------------+
//| This subroutine solves linear system op(A)*x=b where:            |
//|   * A is NxN upper/lower triangular/unitriangular matrix         |
//|   * X and B are Nx1 vectors                                      |
//|  *"op" may be identity transformation or transposition         |
//| Solution replaces X.                                             |
//| IMPORTANT:                                                       |
//|   * no overflow/underflow/denegeracy tests is performed.         |
//|   * no integrity checks for operand sizes, out-of-bounds accesses|
//|     and so on is performed                                       |
//| INPUT PARAMETERS:                                                |
//|   N        -  matrix size, N>=0                                  |
//|   A        -  matrix, actial matrix is stored                    |
//|               in A[IA:IA+N-1,JA:JA+N-1]                          |
//|   IA       -  submatrix offset                                   |
//|   JA       -  submatrix offset                                   |
//|   IsUpper  -  whether matrix is upper triangular                 |
//|   IsUnit   -  whether matrix is unitriangular                    |
//|   OpType   -  transformation type:                               |
//|               * 0 - no transformation                            |
//|               * 1 - transposition                                |
//|   X        -  right part, actual vector is stored in X[IX:IX+N-1]|
//|   IX       -  offset                                             |
//| OUTPUT PARAMETERS:                                               |
//|   X        -  solution replaces elements X[IX:IX+N-1]            |
//+------------------------------------------------------------------+
void CAblasF::RTrsVX(int n,CMatrixDouble &a,int ia,int ja,
                     bool IsUpper,bool IsUnit,int OpType,
                     CRowDouble &x,int ix)
  {
//--- create variables
   int    i=0;
   int    j=0;
   double v=0;
//--- check
   if(n<=0)
      return;
   switch(OpType)
     {
      case 0:
         if(IsUpper)
           {
            for(i=n-1; i>=0; i--)
              {
               v=x[ix+i];
               for(j=i+1; j<n; j++)
                  v-=a.Get(ia+i,ja+j)*x[ix+j];
               if(!IsUnit)
                  v/=a.Get(ia+i,ja+i);
               x.Set(ix+i,v);
              }
           }
         else
           {
            for(i=0; i<n; i++)
              {
               v=x[ix+i];
               for(j=0; j<i; j++)
                  v-=a.Get(ia+i,ja+j)*x[ix+j];
               if(!IsUnit)
                  v/=a.Get(ia+i,ja+i);
               x.Set(ix+i,v);
              }
           }
         break;
      case 1:
         if(IsUpper)
           {
            for(i=0; i<n; i++)
              {
               v=x[ix+i];
               if(v==0)
                  continue;
               if(!IsUnit)
                 {
                  v/=a.Get(ia+i,ja+i);
                  x.Set(ix+i,v);
                 }
               for(j=i+1; j<=n-1; j++)
                  x.Set(ix+j,x[ix+j]-v*a.Get(ia+i,ja+j));
              }
           }
         else
           {
            for(i=n-1; i>=0; i--)
              {
               v=x[ix+i];
               if(v==0)
                  continue;
               if(!IsUnit)
                 {
                  v/=a.Get(ia+i,ja+i);
                  x.Set(ix+i,v);
                 }
               for(j=0; j<i; j++)
                  x.Set(ix+j,x[ix+j]-v*a.Get(ia+i,ja+j));
              }
           }
         break;
      default:
         CAp::Assert(false,__FUNCTION__": unexpected operation type");
         break;
     }
  }
//+------------------------------------------------------------------+
//| Computing matrix-vector and matrix-matrix                        |
//+------------------------------------------------------------------+
class CBlas
  {
public:
   static double     VectorNorm2(double &x[],const int i1,const int i2);
   static double     VectorNorm2(CRowDouble &x,const int i1,const int i2);
   static int        VectorIdxAbsMax(double &x[],const int i1,const int i2);
   static int        VectorIdxAbsMax(CRowDouble &x,const int i1,const int i2);
   static int        ColumnIdxAbsMax(CMatrixDouble &x,const int i1,const int i2,const int j);
   static int        RowIdxAbsMax(CMatrixDouble &x,const int j1,const int j2,const int i);
   static double     UpperHessenberg1Norm(CMatrixDouble &a,const int i1,const int i2,const int j1,const int j2,double &work[]);
   static double     UpperHessenberg1Norm(CMatrixDouble &a,const int i1,const int i2,const int j1,const int j2,CRowDouble &work);
   static void       CopyMatrix(CMatrixDouble &a,const int is1,const int is2,const int js1,const int js2,CMatrixDouble &b,const int id1,const int id2,const int jd1,const int jd2);
   static void       InplaceTranspose(CMatrixDouble &a,const int i1,const int i2,const int j1,const int j2,double &work[]);
   static void       InplaceTranspose(CMatrixDouble &a,const int i1,const int i2,const int j1,const int j2,CRowDouble &work);
   static void       CopyAndTranspose(CMatrixDouble &a,const int is1,const int is2,const int js1,const int js2,CMatrixDouble &b,const int id1,const int id2,const int jd1,const int jd2);
   static void       MatrixVectorMultiply(CMatrixDouble &a,const int i1,const int i2,const int j1,const int j2,const bool trans,double &x[],const int ix1,const int ix2,const double alpha,double &y[],const int iy1,const int iy2,const double beta);
   static void       MatrixVectorMultiply(CMatrixDouble &a,const int i1,const int i2,const int j1,const int j2,const bool trans,CRowDouble &x,const int ix1,const int ix2,const double alpha,CRowDouble &y,const int iy1,const int iy2,const double beta);
   static double     PyThag2(double x,double y);
   static void       MatrixMatrixMultiply(CMatrixDouble &a,const int ai1,const int ai2,const int aj1,const int aj2,const bool transa,CMatrixDouble &b,const int bi1,const int bi2,const int bj1,const int bj2,const bool transb,const double alpha,CMatrixDouble &c,const int ci1,const int ci2,const int cj1,const int cj2,const double beta,double &work[]);
   static void       MatrixMatrixMultiply(CMatrixDouble &a,const int ai1,const int ai2,const int aj1,const int aj2,const bool transa,CMatrixDouble &b,const int bi1,const int bi2,const int bj1,const int bj2,const bool transb,const double alpha,CMatrixDouble &c,const int ci1,const int ci2,const int cj1,const int cj2,const double beta,CRowDouble &work);
  };
//+------------------------------------------------------------------+
//| Vector norm                                                      |
//+------------------------------------------------------------------+
double CBlas::VectorNorm2(double &x[],const int i1,const int i2)
  {
   CRowDouble X=x;
//--- return result
   return(VectorNorm2(X,i1,i2));
  }
//+------------------------------------------------------------------+
//| Vector norm                                                      |
//+------------------------------------------------------------------+
double CBlas::VectorNorm2(CRowDouble &x,const int i1,const int i2)
  {
//--- create variables
   int    n=i2-i1+1;
   int    ix=0;
   double absxi=0;
   double scl=0;
   double ssq=1;
//--- check
   if(n<1)
      return(0);
//--- check
   if(n==1)
      return(MathAbs(x[i1]));
//--- norm
   for(ix=i1; ix<=i2; ix++)
     {
      //--- check
      if(x[ix]!=0.0)
        {
         absxi=MathAbs(x[ix]);
         //--- check
         if(scl<absxi)
           {
            ssq=1+ssq*CMath::Sqr(scl/absxi);
            scl=absxi;
           }
         else
            ssq+=CMath::Sqr(absxi/scl);
        }
     }
//--- return result
   return(scl*MathSqrt(ssq));
  }
//+------------------------------------------------------------------+
//| Internal subroutine                                              |
//+------------------------------------------------------------------+
int CBlas::VectorIdxAbsMax(double &x[],const int i1,const int i2)
  {
   CRowDouble X=x;
   return(VectorIdxAbsMax(X,i1,i2));
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int CBlas::VectorIdxAbsMax(CRowDouble &x,const int i1,const int i2)
  {
   int result=i1;
//--- calculation
   for(int i=i1+1; i<=i2; i++)
     {
      //--- check
      if(MathAbs(x[i])>MathAbs(x[result]))
         result=i;
     }
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| Internal subroutine                                              |
//+------------------------------------------------------------------+
int CBlas::ColumnIdxAbsMax(CMatrixDouble &x,const int i1,const int i2,const int j)
  {
   CRowDouble temp=x.Col(j)+0;
//--- return result
   return(VectorIdxAbsMax(temp,i1,i2));
  }
//+------------------------------------------------------------------+
//| Internal subroutine                                              |
//+------------------------------------------------------------------+
int CBlas::RowIdxAbsMax(CMatrixDouble &x,const int j1,const int j2,const int i)
  {
   CRowDouble temp=x[i]+0;
//--- return result
   return(VectorIdxAbsMax(temp,j1,j2));
  }
//+------------------------------------------------------------------+
//| Upper Hessenberg norm                                            |
//+------------------------------------------------------------------+
double CBlas::UpperHessenberg1Norm(CMatrixDouble &a,const int i1,
                                   const int i2,const int j1,
                                   const int j2,double &work[])
  {
   CRowDouble Work=work;
   double result=UpperHessenberg1Norm(a,i1,i2,j1,j2,Work);
   Work.ToArray(work);
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double CBlas::UpperHessenberg1Norm(CMatrixDouble &a,const int i1,
                                   const int i2,const int j1,
                                   const int j2,CRowDouble &work)
  {
//--- create variables
   double result=0;
   int    i=0;
   int    j=0;
//--- check
   if(!CAp::Assert(i2-i1==j2-j1,__FUNCTION__+": I2-I1!=J2-J1!"))
      return(EMPTY_VALUE);
   for(j=j1; j<=j2; j++)
      work.Set(j,0);
   for(i=i1; i<=i2; i++)
     {
      for(j=MathMax(j1,j1+i-i1-1); j<=j2; j++)
         work.Set(j,work[j]+MathAbs(a.Get(i,j)));
     }
//--- get result
   result=0;
   for(j=j1; j<=j2; j++)
      result=MathMax(result,work[j]);
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| Copy matrix                                                      |
//+------------------------------------------------------------------+
void CBlas::CopyMatrix(CMatrixDouble &a,const int is1,const int is2,
                       const int js1,const int js2,CMatrixDouble &b,
                       const int id1,const int id2,const int jd1,const int jd2)
  {
//--- create variables
   int isrc=0;
   int idst=0;
   int i_=0;
   int i1_=0;
//--- check
   if(is1>is2 || js1>js2)
      return;
//--- check
   if(!CAp::Assert(is2-is1==id2-id1,__FUNCTION__+": different sizes!"))
      return;
//--- check
   if(!CAp::Assert(js2-js1==jd2-jd1,__FUNCTION__+": different sizes!"))
      return;
//--- copy
   for(isrc=is1; isrc<=is2; isrc++)
     {
      idst=isrc-is1+id1;
      i1_=js1-jd1;
      for(i_=jd1; i_<=jd2; i_++)
         b.Set(idst,i_,a.Get(isrc,i_+i1_));
     }
  }
//+------------------------------------------------------------------+
//| Matrix transpose                                                 |
//+------------------------------------------------------------------+
void CBlas::InplaceTranspose(CMatrixDouble &a,const int i1,const int i2,
                             const int j1,const int j2,double &work[])
  {
//--- create variables
   int i=0;
   int j=0;
   int ips=0;
   int jps=0;
   int l=0;
   int i_=0;
   int i1_=0;
//--- check
   if(i1>i2 || j1>j2)
      return;
//--- check
   if(!CAp::Assert(i1-i2==j1-j2,__FUNCTION__+": incorrect array size!"))
      return;
   for(i=i1; i<=i2-1; i++)
     {
      //--- change values
      j=j1+i-i1;
      ips=i+1;
      jps=j1+ips-i1;
      l=i2-i;
      i1_=ips-1;
      //--- transpose
      for(i_=1; i_<=l; i_++)
         work[i_]=a.Get(i_+i1_,j);
      i1_=jps-ips;
      for(i_=ips; i_<=i2; i_++)
         a.Set(i_,j,a.Get(i,i_+i1_));
      i1_=1-jps;
      for(i_=jps; i_<=j2; i_++)
         a.Set(i,i_,work[i_+i1_]);
     }
  }
//+------------------------------------------------------------------+
//| Matrix transpose                                                 |
//+------------------------------------------------------------------+
void CBlas::InplaceTranspose(CMatrixDouble &a,const int i1,const int i2,
                             const int j1,const int j2,CRowDouble &work)
  {
//--- create variables
   int i=0;
   int j=0;
   int ips=0;
   int jps=0;
   int l=0;
   int i_=0;
   int i1_=0;
//--- check
   if(i1>i2 || j1>j2)
      return;
//--- check
   if(!CAp::Assert(i1-i2==j1-j2,__FUNCTION__+": incorrect array size!"))
      return;
   for(i=i1; i<=i2-1; i++)
     {
      //--- change values
      j=j1+i-i1;
      ips=i+1;
      jps=j1+ips-i1;
      l=i2-i;
      i1_=ips-1;
      //--- transpose
      for(i_=1; i_<=l; i_++)
         work.Set(i_,a.Get(i_+i1_,j));
      i1_=jps-ips;
      for(i_=ips; i_<=i2; i_++)
         a.Set(i_,j,a.Get(i,i_+i1_));
      i1_=1-jps;
      for(i_=jps; i_<=j2; i_++)
         a.Set(i,i_,work[i_+i1_]);
     }
  }
//+------------------------------------------------------------------+
//| Copy and transpose matrix                                        |
//+------------------------------------------------------------------+
void CBlas::CopyAndTranspose(CMatrixDouble &a,const int is1,const int is2,
                             const int js1,const int js2,CMatrixDouble &b,
                             const int id1,const int id2,const int jd1,const int jd2)
  {
//--- create variables
   int isrc=0;
   int jdst=0;
   int i_=0;
   int i1_=0;
//--- check
   if(is1>is2 || js1>js2)
      return;
//--- check
   if(!CAp::Assert(is2-is1==jd2-jd1,__FUNCTION__+": different sizes!"))
      return;
//--- check
   if(!CAp::Assert(js2-js1==id2-id1,__FUNCTION__+": different sizes!"))
      return;
//--- copy and transpose
   for(isrc=is1; isrc<=is2; isrc++)
     {
      jdst=isrc-is1+jd1;
      i1_=js1-id1;
      for(i_=id1; i_<=id2; i_++)
         b.Set(i_,jdst,a.Get(isrc,i_+i1_));
     }
  }
//+------------------------------------------------------------------+
//| Matrix vector multiply                                           |
//+------------------------------------------------------------------+
void CBlas::MatrixVectorMultiply(CMatrixDouble &a,const int i1,const int i2,
                                 const int j1,const int j2,const bool trans,
                                 double &x[],const int ix1,const int ix2,
                                 const double alpha,double &y[],const int iy1,
                                 const int iy2,const double beta)
  {
   CRowDouble X=x;
   CRowDouble Y=y;
   MatrixVectorMultiply(a,i1,i2,j1,j2,trans,X,ix1,ix2,alpha,Y,iy1,iy2,beta);
   Y.ToArray(y);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CBlas::MatrixVectorMultiply(CMatrixDouble &a,const int i1,const int i2,
                                 const int j1,const int j2,const bool trans,
                                 CRowDouble &x,const int ix1,const int ix2,
                                 const double alpha,CRowDouble &y,const int iy1,
                                 const int iy2,const double beta)
  {
//--- create variables
   int    i=0;
   double v=0;
   int    i_=0;
   int    i1_=0;
//--- check
   if(!trans)
     {
      //--- y := alpha*A*x + beta*y;
      if(i1>i2 || j1>j2)
         return;
      //--- check
      if(!CAp::Assert(j2-j1==ix2-ix1,__FUNCTION__+": A and X dont match!"))
         return;
      //--- check
      if(!CAp::Assert(i2-i1==iy2-iy1,__FUNCTION__+": A and Y dont match!"))
         return;
      //--- beta*y
      if(beta==0.0)
        {
         for(i=iy1; i<=iy2; i++)
            y.Set(i,0);
        }
      else
        {
         for(i_=iy1; i_<=iy2; i_++)
            y.Set(i_,beta*y[i_]);
        }
      //--- alpha*A*x
      for(i=i1; i<=i2; i++)
        {
         i1_=ix1-j1;
         v=0.0;
         for(i_=j1; i_<=j2; i_++)
            v+=a.Get(i,i_)*x[i_+i1_];
         y.Set((iy1+i-i1),(y[iy1+i-i1]+alpha*v));
        }
     }
   else
     {
      //--- y := alpha*A'*x + beta*y;
      if(i1>i2 || j1>j2)
         return;
      //--- check
      if(!CAp::Assert(i2-i1==ix2-ix1,__FUNCTION__+": A and X dont match!"))
         return;
      //--- check
      if(!CAp::Assert(j2-j1==iy2-iy1,__FUNCTION__+": A and Y dont match!"))
         return;
      //--- beta*y
      if(beta==0.0)
        {
         for(i=iy1; i<=iy2; i++)
            y.Set(i,0);
        }
      else
        {
         for(i_=iy1; i_<=iy2; i_++)
            y.Set(i_,beta*y[i_]);
        }
      //--- alpha*A'*x
      for(i=i1; i<=i2; i++)
        {
         v=alpha*x[ix1+i-i1];
         i1_=j1-iy1;
         for(i_=iy1; i_<=iy2; i_++)
            y.Set(i_,(y[i_]+v*a.Get(i,i_+i1_)));
        }
     }
  }
//+------------------------------------------------------------------+
//| Internal subroutine                                              |
//+------------------------------------------------------------------+
double CBlas::PyThag2(double x,double y)
  {
//--- create variables
   double result=0;
   double xabs=MathAbs(x);
   double yabs=MathAbs(y);
   double w=MathMax(xabs,yabs);
   double z=MathMin(xabs,yabs);
//--- check
   if(z==0.0)
      result=w;
   else
      result=w*MathSqrt(1+CMath::Sqr(z/w));
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| Matrix matrix multiply                                           |
//+------------------------------------------------------------------+
void CBlas::MatrixMatrixMultiply(CMatrixDouble &a,const int ai1,const int ai2,
                                 const int aj1,const int aj2,const bool transa,
                                 CMatrixDouble &b,const int bi1,const int bi2,
                                 const int bj1,const int bj2,const bool transb,
                                 const double alpha,CMatrixDouble &c,const int ci1,
                                 const int ci2,const int cj1,const int cj2,
                                 const double beta,double &work[])
  {
   CRowDouble Work=work;
   MatrixMatrixMultiply(a,ai1,ai2,aj1,aj2,transa,b,bi1,bi2,bj1,bj2,transb,alpha,c,ci1,ci2,cj1,cj2,beta,Work);
   Work.ToArray(work);
  }
//+------------------------------------------------------------------+
//| Matrix matrix multiply                                           |
//+------------------------------------------------------------------+
void CBlas::MatrixMatrixMultiply(CMatrixDouble &a,const int ai1,const int ai2,
                                 const int aj1,const int aj2,const bool transa,
                                 CMatrixDouble &b,const int bi1,const int bi2,
                                 const int bj1,const int bj2,const bool transb,
                                 const double alpha,CMatrixDouble &c,const int ci1,
                                 const int ci2,const int cj1,const int cj2,
                                 const double beta,CRowDouble &work)
  {
//--- create variables
   int    arows=0;
   int    acols=0;
   int    brows=0;
   int    bcols=0;
   int    crows=0;
   int    ccols=0;
   int    i=0;
   int    j=0;
   int    k=0;
   int    l=0;
   int    r=0;
   double v=0;
   int    i_=0;
   int    i1_=0;
//--- Setup
   if(!transa)
     {
      arows=ai2-ai1+1;
      acols=aj2-aj1+1;
     }
   else
     {
      arows=aj2-aj1+1;
      acols=ai2-ai1+1;
     }
//--- check
   if(!transb)
     {
      brows=bi2-bi1+1;
      bcols=bj2-bj1+1;
     }
   else
     {
      brows=bj2-bj1+1;
      bcols=bi2-bi1+1;
     }
//--- check
   if(!CAp::Assert(acols==brows,__FUNCTION__+": incorrect matrix sizes!"))
      return;
//--- check
   if(arows<=0 || acols<=0 || brows<=0 || bcols<=0)
      return;
   crows=arows;
   ccols=bcols;
//--- Test WORK
   i=MathMax(arows,acols);
   i=MathMax(brows,i);
   i=MathMax(i,bcols);
   work.Set(1,0);
   work.Set(i,0);
//--- Prepare C
   if(beta==0.0)
     {
      for(i=ci1; i<=ci2; i++)
         for(j=cj1; j<=cj2; j++)
            c.Set(i,j,0);
     }
   else
     {
      for(i=ci1; i<=ci2; i++)
         for(i_=cj1; i_<=cj2; i_++)
            c.Mul(i,i_,beta);
     }
//--- A*B
   if(!transa && !transb)
     {
      for(l=ai1; l<=ai2; l++)
        {
         for(r=bi1; r<=bi2; r++)
           {
            //--- change values
            v=alpha*a.Get(l,aj1+r-bi1);
            k=ci1+l-ai1;
            i1_=bj1-cj1;
            for(i_=cj1; i_<=cj2; i_++)
               c.Add(k,i_,v*b.Get(r,i_+i1_));
           }
        }
      //--- exit the function
      return;
     }
//--- A*B'
   if(!transa && transb)
     {
      //--- check
      if(arows*acols<brows*bcols)
        {
         for(r=bi1; r<=bi2; r++)
           {
            for(l=ai1; l<=ai2; l++)
              {
               //--- change values
               i1_=bj1-aj1;
               v=0.0;
               for(i_=aj1; i_<=aj2; i_++)
                  v+=a.Get(l,i_)*b.Get(r,i_+i1_);
               c.Add(ci1+l-ai1,cj1+r-bi1,alpha*v);
              }
           }
         //--- exit the function
         return;
        }
      else
        {
         for(l=ai1; l<=ai2; l++)
           {
            for(r=bi1; r<=bi2; r++)
              {
               //--- change values
               i1_=bj1-aj1;
               v=0.0;
               for(i_=aj1; i_<=aj2; i_++)
                  v+=a.Get(l,i_)*b.Get(r,i_+i1_);
               c.Set(ci1+l-ai1,cj1+r-bi1,c.Get(ci1+l-ai1,cj1+r-bi1)+alpha*v);
              }
           }
         //--- exit the function
         return;
        }
     }
//--- A'*B
   if(transa && !transb)
     {
      for(l=aj1; l<=aj2; l++)
        {
         for(r=bi1; r<=bi2; r++)
           {
            //--- change values
            v=alpha*a.Get(ai1+r-bi1,l);
            k=ci1+l-aj1;
            i1_=bj1-cj1;
            for(i_=cj1; i_<=cj2; i_++)
               c.Set(k,i_,c.Get(k,i_)+v*b.Get(r,i_+i1_));
           }
        }
      //--- exit the function
      return;
     }
//--- A'*B'
   if(transa && transb)
     {
      //--- check
      if(arows*acols<brows*bcols)
        {
         for(r=bi1; r<=bi2; r++)
           {
            k=cj1+r-bi1;
            for(i=1; i<=crows; i++)
               work.Set(i,0.0);
            for(l=ai1; l<=ai2; l++)
              {
               //--- change values
               v=alpha*b.Get(r,bj1+l-ai1);
               i1_=aj1-1;
               for(i_=1; i_<=crows; i_++)
                  work.Set(i_,work[i_]+v*a.Get(l,i_+i1_));
              }
            i1_=1-ci1;
            for(i_=ci1; i_<=ci2; i_++)
               c.Set(i_,k,c.Get(i_,k)+work[i_+i1_]);
           }
        }
      else
        {
         for(l=aj1; l<=aj2; l++)
           {
            k=ai2-ai1+1;
            i1_=ai1-1;
            for(i_=1; i_<=k; i_++)
               work.Set(i_,a.Get(i_+i1_,l));
            for(r=bi1; r<=bi2; r++)
              {
               //--- change values
               i1_=bj1-1;
               v=0.0;
               for(i_=1; i_<=k; i_++)
                  v+=work[i_]*b.Get(r,i_+i1_);
               c.Set(ci1+l-aj1,cj1+r-bi1,c.Get(ci1+l-aj1,cj1+r-bi1)+alpha*v);
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+
//| Work with the Hermitian matrix                                   |
//+------------------------------------------------------------------+
class CHblas
  {
public:
   static void       HermitianMatrixVectorMultiply(CMatrixComplex &a,const bool IsUpper,const int i1,const int i2,complex &x[],complex &alpha,complex &y[]);
   static void       HermitianMatrixVectorMultiply(CMatrixComplex &a,const bool IsUpper,const int i1,const int i2,CRowComplex &x,complex &alpha,CRowComplex &y);
   static void       HermitianRank2Update(CMatrixComplex &a,const bool IsUpper,const int i1,const int i2,complex &x[],complex &y[],complex &t[],complex &alpha);
   static void       HermitianRank2Update(CMatrixComplex &a,const bool IsUpper,const int i1,const int i2,CRowComplex &x,CRowComplex &y,CRowComplex &t,complex &alpha);
  };
//+------------------------------------------------------------------+
//| Multiply                                                         |
//+------------------------------------------------------------------+
void CHblas::HermitianMatrixVectorMultiply(CMatrixComplex &a,const bool IsUpper,
                                           const int i1,const int i2,complex &x[],
                                           complex &alpha,complex &y[])
  {
//--- create variables
   int     i=0;
   int     ba1=0;
   int     ba2=0;
   int     by1=0;
   int     by2=0;
   int     bx1=0;
   int     bx2=0;
   int     n=i2-i1+1;
   int     i_=0;
   int     i1_=0;
   complex v=0;
//--- check
   if(n<=0)
      return;
//--- Let A = L + D + U, where
//---  L is strictly lower triangular (main diagonal is zero)
//---  D is diagonal
//---  U is strictly upper triangular (main diagonal is zero)
//--- A*x = L*x + D*x + U*x
//--- Calculate D*x first
   for(i=i1; i<=i2; i++)
      y[i-i1+1]=a.Get(i,i)*x[i-i1+1];
//--- Add L*x + U*x
   if(IsUpper)
     {
      for(i=i1; i<i2; i++)
        {
         //--- Add L*x to the result
         v=x[i-i1+1];
         by1=i-i1+2;
         by2=n;
         ba1=i+1;
         ba2=i2;
         i1_=ba1-by1;
         for(i_=by1; i_<=by2; i_++)
            y[i_]=y[i_]+v*CMath::Conj(a.Get(i,i_+i1_));
         //--- Add U*x to the result
         bx1=i-i1+2;
         bx2=n;
         ba1=i+1;
         ba2=i2;
         i1_=ba1-bx1;
         v=0.0;
         for(i_=bx1; i_<=bx2; i_++)
            v+=x[i_]*a.Get(i,i_+i1_);
         y[i-i1+1]=y[i-i1+1]+v;
        }
     }
   else
     {
      for(i=i1+1; i<=i2; i++)
        {
         //--- Add L*x to the result
         bx1=1;
         bx2=i-i1;
         ba1=i1;
         ba2=i-1;
         i1_=ba1-bx1;
         v=0.0;
         for(i_=bx1; i_<=bx2; i_++)
            v+=x[i_]*a.Get(i,i_+i1_);
         y[i-i1+1]=y[i-i1+1]+v;
         //--- change parameters
         v=x[i-i1+1];
         by1=1;
         by2=i-i1;
         ba1=i1;
         ba2=i-1;
         i1_=ba1-by1;
         //--- Add U*x to the result
         for(i_=by1; i_<=by2; i_++)
            y[i_]=y[i_]+v*CMath::Conj(a.Get(i,i_+i1_));
        }
     }
//--- get result
   for(i_=1; i_<=n; i_++)
      y[i_]=alpha*y[i_];
  }
//+------------------------------------------------------------------+
//| Multiply                                                         |
//+------------------------------------------------------------------+
void CHblas::HermitianMatrixVectorMultiply(CMatrixComplex &a,const bool IsUpper,
                                           const int i1,const int i2,CRowComplex &x,
                                           complex &alpha,CRowComplex &y)
  {
//--- create variables
   int     i=0;
   int     ba1=0;
   int     ba2=0;
   int     by1=0;
   int     by2=0;
   int     bx1=0;
   int     bx2=0;
   int     n=i2-i1+1;
   int     i_=0;
   int     i1_=0;
   complex v=0;
//--- check
   if(n<=0)
      return;
//--- Let A = L + D + U, where
//---  L is strictly lower triangular (main diagonal is zero)
//---  D is diagonal
//---  U is strictly upper triangular (main diagonal is zero)
//--- A*x = L*x + D*x + U*x
//--- Calculate D*x first
   for(i=i1; i<=i2; i++)
      y.Set(i-i1+1,a.Get(i,i)*x[i-i1+1]);
//--- Add L*x + U*x
   if(IsUpper)
     {
      for(i=i1; i<i2; i++)
        {
         //--- Add L*x to the result
         v=x[i-i1+1];
         by1=i-i1+2;
         by2=n;
         ba1=i+1;
         ba2=i2;
         i1_=ba1-by1;
         for(i_=by1; i_<=by2; i_++)
           {
            complex value=y[i_]+v*CMath::Conj(a.Get(i,i_+i1_));
            y.Set(i_,value);
           }
         //--- Add U*x to the result
         bx1=i-i1+2;
         bx2=n;
         ba1=i+1;
         ba2=i2;
         i1_=ba1-bx1;
         v=y[i-i1+1];
         for(i_=bx1; i_<=bx2; i_++)
            v+=x[i_]*a.Get(i,i_+i1_);
         y.Set(i-i1+1,v);
        }
     }
   else
     {
      for(i=i1+1; i<=i2; i++)
        {
         //--- Add L*x to the result
         bx1=1;
         bx2=i-i1;
         ba1=i1;
         ba2=i-1;
         i1_=ba1-bx1;
         v=y[i-i1+1];
         for(i_=bx1; i_<=bx2; i_++)
            v+=x[i_]*a.Get(i,i_+i1_);
         y.Set(i-i1+1,v);
         //--- change parameters
         v=x[i-i1+1];
         by1=1;
         by2=i-i1;
         ba1=i1;
         ba2=i-1;
         i1_=ba1-by1;
         //--- Add U*x to the result
         for(i_=by1; i_<=by2; i_++)
           {
            complex value=y[i_]+v*CMath::Conj(a.Get(i,i_+i1_));
            y.Set(i_,value);
           }
        }
     }
//--- get result
   y*=alpha;
  }
//+------------------------------------------------------------------+
//| Update matrix                                                    |
//+------------------------------------------------------------------+
void CHblas::HermitianRank2Update(CMatrixComplex &a,const bool IsUpper,
                                  const int i1,const int i2,complex &x[],
                                  complex &y[],complex &t[],complex &alpha)
  {
//--- create variables
   int     i=0;
   int     tp1=0;
   int     tp2=0;
   complex v=0;
   int     i_=0;
   int     i1_=0;
//--- check
   if(IsUpper)
     {
      for(i=i1; i<=i2; i++)
        {
         //--- change values
         tp1=i+1-i1;
         tp2=i2-i1+1;
         v=alpha*x[tp1];
         //--- change t
         for(i_=tp1; i_<=tp2; i_++)
            t[i_]=v*CMath::Conj(y[i_]);
         v=CMath::Conj(alpha)*y[tp1];
         //--- change t
         for(i_=tp1; i_<=tp2; i_++)
            t[i_]=t[i_]+v*CMath::Conj(x[i_]);
         i1_=tp1-i;
         //--- change a
         for(i_=i; i_<=i2; i_++)
            a.Set(i,i_,a.Get(i,i_)+t[i_+i1_]);
        }
     }
   else
     {
      for(i=i1; i<=i2; i++)
        {
         //--- change values
         tp1=1;
         tp2=i+1-i1;
         v=alpha*x[tp2];
         //--- change t
         for(i_=tp1; i_<=tp2; i_++)
            t[i_]=v*CMath::Conj(y[i_]);
         v=CMath::Conj(alpha)*y[tp2];
         //--- change t
         for(i_=tp1; i_<=tp2; i_++)
            t[i_]=t[i_]+v*CMath::Conj(x[i_]);
         i1_=tp1-i1;
         //--- change a
         for(i_=i1; i_<=i; i_++)
            a.Set(i,i_,a.Get(i,i_)+t[i_+i1_]);
        }
     }
  }
//+------------------------------------------------------------------+
//| Update matrix                                                    |
//+------------------------------------------------------------------+
void CHblas::HermitianRank2Update(CMatrixComplex &a,const bool IsUpper,
                                  const int i1,const int i2,CRowComplex &x,
                                  CRowComplex &y,CRowComplex &t,complex &alpha)
  {
//--- create variables
   int     i=0;
   int     tp1=0;
   int     tp2=0;
   complex v=0;
   int     i_=0;
   int     i1_=0;
//--- check
   if(IsUpper)
     {
      for(i=i1; i<=i2; i++)
        {
         //--- change values
         tp1=i+1-i1;
         tp2=i2-i1+1;
         v=alpha*x[tp1];
         //--- change t
         for(i_=tp1; i_<=tp2; i_++)
           {
            complex value=v*CMath::Conj(y[i_]);
            t.Set(i_,value);
           }
         v=CMath::Conj(alpha)*y[tp1];
         //--- change t
         for(i_=tp1; i_<=tp2; i_++)
           {
            complex value=t[i_]+v*CMath::Conj(x[i_]);
            t.Set(i_,value);
           }
         i1_=tp1-i;
         //--- change a
         for(i_=i; i_<=i2; i_++)
            a.Set(i,i_,a.Get(i,i_)+t[i_+i1_]);
        }
     }
   else
     {
      for(i=i1; i<=i2; i++)
        {
         //--- change values
         tp1=1;
         tp2=i+1-i1;
         v=alpha*x[tp2];
         //--- change t
         for(i_=tp1; i_<=tp2; i_++)
           {
            complex value=v*CMath::Conj(y[i_]);
            t.Set(i_,value);
           }
         v=CMath::Conj(alpha)*y[tp2];
         //--- change t
         for(i_=tp1; i_<=tp2; i_++)
           {
            complex value=t[i_]+v*CMath::Conj(x[i_]);
            t.Set(i_,value);
           }
         i1_=tp1-i1;
         //--- change a
         for(i_=i1; i_<=i; i_++)
            a.Set(i,i_,a.Get(i,i_)+t[i_+i1_]);
        }
     }
  }
//+------------------------------------------------------------------+
//| Reflections                                                      |
//+------------------------------------------------------------------+
class CReflections
  {
public:
   static void       GenerateReflection(double &x[],const int n,double &tau);
   static void       GenerateReflection(CRowDouble &x,const int n,double &tau);
   static void       ApplyReflectionFromTheLeft(CMatrixDouble &c,const double tau,const double &v[],const int m1,const int m2,const int n1,const int n2,double &work[]);
   static void       ApplyReflectionFromTheLeft(CMatrixDouble &c,const double tau,const CRowDouble &v,const int m1,const int m2,const int n1,const int n2,CRowDouble &work);
   static void       ApplyReflectionFromTheRight(CMatrixDouble &c,const double tau,const double &v[],const int m1,const int m2,const int n1,const int n2,double &work[]);
   static void       ApplyReflectionFromTheRight(CMatrixDouble &c,const double tau,const CRowDouble &v,const int m1,const int m2,const int n1,const int n2,CRowDouble &work);
  };
//+------------------------------------------------------------------+
//| Generation of an elementary reflection transformation            |
//| The subroutine generates elementary reflection H of order N, so  |
//| that, for a given X, the following equality holds true:          |
//|     ( X(1) )   ( Beta )                                          |
//| H * (  ..  ) = (  0   )                                          |
//|     ( X(n) )   (  0   )                                          |
//| where                                                            |
//|               ( V(1) )                                           |
//| H = 1 - Tau * (  ..  ) * ( V(1), ..., V(n) )                     |
//|               ( V(n) )                                           |
//| where the first component of vector V equals 1.                  |
//| Input parameters:                                                |
//|     X   -   vector. Array whose index ranges within [1..N].      |
//|     N   -   reflection order.                                    |
//| Output parameters:                                               |
//|     X   -   components from 2 to N are replaced with vector V.   |
//|             The first component is replaced with parameter Beta. |
//|     Tau -   scalar value Tau. If X is a null vector, Tau equals  |
//|             0, otherwise 1 <= Tau <= 2.                          |
//| This subroutine is the modification of the DLARFG subroutines    |
//| from the LAPACK library.                                         |
//| MODIFICATIONS:                                                   |
//|     24.12.2005 sign(Alpha) was replaced with an analogous to the |
//|     Fortran SIGN code.                                           |
//|   -- LAPACK auxiliary routine (version 3.0) --                   |
//|      Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., |
//|      Courant Institute, Argonne National Lab, and Rice University|
//|      September 30, 1994                                          |
//+------------------------------------------------------------------+
void CReflections::GenerateReflection(double &x[],const int n,
                                      double &tau)
  {
   CRowDouble X=x;
   GenerateReflection(X,n,tau);
   X.ToArray(x);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CReflections::GenerateReflection(CRowDouble &x,const int n,
                                      double &tau)
  {
//--- check
   if(n<=1)
     {
      tau=0;
      //--- exit the function
      return;
     }
//--- create variables
   int    j=0;
   double alpha=0;
   double xnorm=0;
   double v=0;
   double beta=0;
   double mx=0;
   double s=0;
   int    i_=0;
//--- Scale if needed (to avoid overflow/underflow during intermediate
//--- calculations).
   for(j=1; j<=n; j++)
      mx=MathMax(MathAbs(x[j]),mx);
   s=1;
//--- check
   if(mx!=0.0)
     {
      //--- check
      if(mx<=CMath::m_minrealnumber/CMath::m_machineepsilon)
        {
         //--- change parameters
         s=CMath::m_minrealnumber/CMath::m_machineepsilon;
         v=1/s;
         //--- change x
         CAblasF::RMulVX(n,v,x,1);
         mx=mx*v;
        }
      else
        {
         //--- check
         if(mx>=CMath::m_maxrealnumber*CMath::m_machineepsilon)
           {
            //--- change parameters
            s=CMath::m_maxrealnumber*CMath::m_machineepsilon;
            v=1/s;
            //--- change x
            CAblasF::RMulVX(n,v,x,1);
            mx=mx*v;
           }
        }
     }
//--- XNORM = DNRM2( N-1, X, INCX )
   alpha=x[1];
   xnorm=0;
//--- check
   if(mx!=0.0)
     {
      for(j=2; j<=n; j++)
         xnorm=xnorm+CMath::Sqr(x[j]/mx);
      xnorm=MathSqrt(xnorm)*mx;
     }
//--- check
   if(xnorm==0.0)
     {
      //--- H = I
      tau=0;
      x.Set(1,x[1]*s);
      //--- exit the function
      return;
     }
//--- general case
   mx=MathMax(MathAbs(alpha),MathAbs(xnorm));
   beta=-(mx*MathSqrt(CMath::Sqr(alpha/mx)+CMath::Sqr(xnorm/mx)));
//--- check
   if(alpha<0.0)
      beta=-beta;
//--- change parameters
   tau=(beta-alpha)/beta;
   v=1/(alpha-beta);
//--- change x
   CAblasF::RMulVX(n-1,v,x,2);
//--- Scale back outputs
   x.Set(1,beta*s);
  }
//+------------------------------------------------------------------+
//| Application of an elementary reflection to a rectangular matrix  |
//| of size MxN                                                      |
//| The algorithm pre-multiplies the matrix by an elementary         |
//| reflection transformation which is given by column V and scalar  |
//| Tau (see the description of the GenerateReflection procedure).   |
//| Not the whole matrix but only a part of it is transformed (rows  |
//| from M1 to M2, columns from N1 to N2). Only the elements of this |
//| submatrix are changed.                                           |
//| Input parameters:                                                |
//|     C       -   matrix to be transformed.                        |
//|     Tau     -   scalar defining the transformation.              |
//|     V       -   column defining the transformation.              |
//|                 Array whose index ranges within [1..M2-M1+1].    |
//|     M1, M2  -   range of rows to be transformed.                 |
//|     N1, N2  -   range of columns to be transformed.              |
//|     WORK    -   working array whose indexes goes from N1 to N2.  |
//| Output parameters:                                               |
//|     C       -   the result of multiplying the input matrix C by  |
//|                 the transformation matrix which is given by Tau  |
//|                 and V. If N1>N2 or M1>M2, C is not modified.     |
//|   -- LAPACK auxiliary routine (version 3.0) --                   |
//|      Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., |
//|      Courant Institute, Argonne National Lab, and Rice University|
//|      September 30, 1994                                          |
//+------------------------------------------------------------------+
void CReflections::ApplyReflectionFromTheLeft(CMatrixDouble &c,const double tau,
                                              const double &v[],const int m1,
                                              const int m2,const int n1,
                                              const int n2,double &work[])
  {
//--- check
   if(tau==0.0 || n1>n2 || m1>m2)
      return;
//--- create variables
   double t=0;
   int    i=0;
   int    vm=0;
   int    i_=0;
//--- w := C' * v
   vm=m2-m1+1;
   for(i=n1; i<=n2; i++)
      work[i]=0;
   for(i=m1; i<=m2; i++)
     {
      t=v[i+1-m1];
      //--- change array
      for(i_=n1; i_<=n2; i_++)
         work[i_]+=t*c[i][i_];
     }
//--- C := C - tau * v * w'
   for(i=m1; i<=m2; i++)
     {
      t=v[i-m1+1]*tau;
      for(i_=n1; i_<=n2; i_++)
         c.Set(i,i_,c[i][i_]-t*work[i_]);
     }
  }
//+------------------------------------------------------------------+
//| Application of an elementary reflection to a rectangular matrix  |
//| of size MxN                                                      |
//| The algorithm post-multiplies the matrix by an elementary        |
//| reflection transformation which is given by column V and scalar  |
//| Tau (see the description of the GenerateReflection procedure).   |
//| Not the whole matrix but only a part of it is transformed (rows  |
//| from M1 to M2, columns from N1 to N2). Only the elements of this |
//| submatrix are changed.                                           |
//| Input parameters:                                                |
//|     C       -   matrix to be transformed.                        |
//|     Tau     -   scalar defining the transformation.              |
//|     V       -   column defining the transformation.              |
//|                 Array whose index ranges within [1..N2-N1+1].    |
//|     M1, M2  -   range of rows to be transformed.                 |
//|     N1, N2  -   range of columns to be transformed.              |
//|     WORK    -   working array whose indexes goes from M1 to M2.  |
//| Output parameters:                                               |
//|     C       -   the result of multiplying the input matrix C by  |
//|                 the transformation matrix which is given by Tau  |
//|                 and V. If N1>N2 or M1>M2, C is not modified.     |
//|   -- LAPACK auxiliary routine (version 3.0) --                   |
//|      Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., |
//|      Courant Institute, Argonne National Lab, and Rice University|
//|      September 30, 1994                                          |
//+------------------------------------------------------------------+
void CReflections::ApplyReflectionFromTheRight(CMatrixDouble &c,const double tau,
                                               const double &v[],const int m1,
                                               const int m2,const int n1,
                                               const int n2,double &work[])
  {
   CRowDouble V=v;
   CRowDouble Work=work;
   ApplyReflectionFromTheRight(c,tau,V,m1,m2,n1,n2,Work);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CReflections::ApplyReflectionFromTheRight(CMatrixDouble &c,const double tau,
                                               const CRowDouble &v,const int m1,
                                               const int m2,const int n1,
                                               const int n2,CRowDouble &work)
  {
//--- check
   if(tau==0.0 || n1>n2 || m1>m2)
      return;
//--- create variables
   double t=0;
   int    i=0;
   int    vm=n2-n1+1;
   int    i_=0;
   int    i1_=0;
//--- change matrix
   for(i=m1; i<=m2; i++)
     {
      i1_=1-n1;
      t=0.0;
      //--- calculation parameters
      for(i_=n1; i_<=n2; i_++)
         t+=c[i][i_]*v[i_+i1_];
      t=t*tau;
      i1_=1-n1;
      for(i_=n1; i_<=n2; i_++)
         c.Set(i,i_,c[i][i_]-t*v[i_+i1_]);
     }
  }
//+------------------------------------------------------------------+
//| Complex reflections                                              |
//+------------------------------------------------------------------+
class CComplexReflections
  {
public:
   static void       ComplexGenerateReflection(complex &x[],const int n,complex &tau);
   static void       ComplexGenerateReflection(CRowComplex &x,const int n,complex &tau);
   static void       ComplexApplyReflectionFromTheLeft(CMatrixComplex &c,complex tau,complex &v[],const int m1,const int m2,const int n1,const int n2,complex &work[]);
   static void       ComplexApplyReflectionFromTheLeft(CMatrixComplex &c,complex tau,CRowComplex &v,const int m1,const int m2,const int n1,const int n2,CRowComplex &work);
   static void       ComplexApplyReflectionFromTheRight(CMatrixComplex &c,complex tau,complex &v[],const int m1,const int m2,const int n1,const int n2,complex &work[]);
   static void       ComplexApplyReflectionFromTheRight(CMatrixComplex &c,complex tau,CRowComplex &v,const int m1,const int m2,const int n1,const int n2,CRowComplex &work);
  };
//+------------------------------------------------------------------+
//| Generation of an elementary complex reflection transformation    |
//| The subroutine generates elementary complex reflection H of      |
//| order N, so that, for a given X, the following equality holds    |
//| true:                                                            |
//|      ( X(1) )   ( Beta )                                         |
//| H' * (  ..  ) = (  0   ),   H'*H = I,   Beta is a real number    |
//|      ( X(n) )   (  0   )                                         |
//| where                                                            |
//|               ( V(1) )                                           |
//| H = 1 - Tau * (  ..  ) * ( conj(V(1)), ..., conj(V(n)) )         |
//|               ( V(n) )                                           |
//| where the first component of vector V equals 1.                  |
//| Input parameters:                                                |
//|     X   -   vector. Array with elements [1..N].                  |
//|     N   -   reflection order.                                    |
//| Output parameters:                                               |
//|     X   -   components from 2 to N are replaced by vector V.     |
//|             The first component is replaced with parameter Beta. |
//|     Tau -   scalar value Tau.                                    |
//| This subroutine is the modification of CLARFG subroutines  from  |
//| the LAPACK library. It has similar functionality except for the  |
//| fact that it  doesn?t handle errors when intermediate results    |
//| cause an overflow.                                               |
//|   -- LAPACK auxiliary routine (version 3.0) --                   |
//|      Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., |
//|      Courant Institute, Argonne National Lab, and Rice University|
//|      September 30, 1994                                          |
//+------------------------------------------------------------------+
void CComplexReflections::ComplexGenerateReflection(complex &x[],
                                                    const int n,
                                                    complex &tau)
  {
//--- check
   if(n<=0)
     {
      tau=0;
      //--- exit the function
      return;
     }
//--- create variables
   int     j=0;
   complex alpha=0;
   double  alphi=0;
   double  alphr=0;
   double  beta=0;
   double  xnorm=0;
   double  mx=0;
   complex t=0;
   double  s=1;
   complex v=0;
   int     i_=0;
   complex One(1,0);
//--- Scale if needed (to avoid overflow/underflow during intermediate
//--- calculations).
   for(j=1; j<=n; j++)
      mx=MathMax(CMath::AbsComplex(x[j]),mx);
//--- check
   if(mx!=0)
     {
      //--- check
      if(mx<1)
        {
         s=MathSqrt(CMath::m_minrealnumber);
         v=1/s;
         //--- change x
         for(i_=1; i_<=n; i_++)
            x[i_]=v*x[i_];
        }
      else
        {
         s=MathSqrt(CMath::m_maxrealnumber);
         v=1/s;
         //--- change x
         for(i_=1; i_<=n; i_++)
            x[i_]=v*x[i_];
        }
     }
//--- calculate
   alpha=x[1];
   mx=0;
   for(j=2; j<=n; j++)
      mx=MathMax(CMath::AbsComplex(x[j]),mx);
   xnorm=0;
//--- check
   if(mx!=0)
     {
      for(j=2; j<=n; j++)
        {
         t=x[j]/mx;
         xnorm=xnorm+(t*CMath::Conj(t)).real;
        }
      xnorm=MathSqrt(xnorm)*mx;
     }
//--- change parameters
   alphr=alpha.real;
   alphi=alpha.imag;
//--- check
   if((xnorm==0) && (alphi==0))
     {
      tau=0;
      x[1]=x[1]*s;
      //--- exit the function
      return;
     }
//--- change parameters
   mx=MathMax(MathAbs(alphr),MathAbs(alphi));
   mx=MathMax(mx,MathAbs(xnorm));
   beta=-(mx*MathSqrt(CMath::Sqr(alphr/mx)+CMath::Sqr(alphi/mx)+CMath::Sqr(xnorm/mx)));
//--- check
   if(alphr<0)
      beta=-beta;
//--- change parameters
   tau.real=(beta-alphr)/beta;
   tau.imag=-(alphi/beta);
   alpha=One/(alpha-beta);
//--- check
   if(n>1)
     {
      //--- change x
      for(i_=2; i_<=n; i_++)
         x[i_]=alpha*x[i_];
     }
   alpha=beta;
   x[1]=alpha;
//--- Scale back
   x[1]=x[1]*s;
  }
//+------------------------------------------------------------------+
//| Same                                                             |
//+------------------------------------------------------------------+
void CComplexReflections::ComplexGenerateReflection(CRowComplex &x,
                                                    const int n,
                                                    complex &tau)
  {
//--- check
   if(n<=0)
     {
      tau=0;
      //--- exit the function
      return;
     }
//--- create variables
   int     j=0;
   complex alpha=0;
   double  alphi=0;
   double  alphr=0;
   double  beta=0;
   double  xnorm=0;
   double  mx=0;
   complex t=0;
   double  s=1;
   complex v=0;
   int     i_=0;
   complex One(1,0);
//--- Scale if needed (to avoid overflow/underflow during intermediate
//--- calculations).
   for(j=1; j<=n; j++)
      mx=MathMax(CMath::AbsComplex(x[j]),mx);
//--- check
   if(mx!=0)
     {
      //--- check
      if(mx<1)
         s=MathSqrt(CMath::m_minrealnumber);
      else
         s=MathSqrt(CMath::m_maxrealnumber);
      v=1/s;
      //--- change x
      for(i_=1; i_<=n; i_++)
         x.Set(i_,v*x[i_]);
     }
//--- calculate
   alpha=x[1];
   mx=0;
   for(j=2; j<=n; j++)
      mx=MathMax(CMath::AbsComplex(x[j]),mx);
   xnorm=0;
//--- check
   if(mx!=0)
     {
      for(j=2; j<=n; j++)
        {
         t=x[j]/mx;
         xnorm+=(t*CMath::Conj(t)).real;
        }
      xnorm=MathSqrt(xnorm)*mx;
     }
//--- change parameters
   alphr=alpha.real;
   alphi=alpha.imag;
//--- check
   if((xnorm==0) && (alphi==0))
     {
      tau=0;
      x.Mul(1,s);
      //--- exit the function
      return;
     }
//--- change parameters
   mx=MathMax(MathAbs(alphr),MathAbs(alphi));
   mx=MathMax(mx,MathAbs(xnorm));
   beta=-(mx*MathSqrt(CMath::Sqr(alphr/mx)+CMath::Sqr(alphi/mx)+CMath::Sqr(xnorm/mx)));
//--- check
   if(alphr<0)
      beta=-beta;
//--- change parameters
   tau.real=(beta-alphr)/beta;
   tau.imag=-(alphi/beta);
   alpha=One/(alpha-beta);
//--- check
   if(n>1)
     {
      //--- change x
      for(i_=2; i_<=n; i_++)
         x.Mul(i_,alpha);
     }
   alpha=beta;
//--- Scale back
   x.Set(1,alpha*s);
  }
//+------------------------------------------------------------------+
//| Application of an elementary reflection to a rectangular matrix  |
//| of size MxN                                                      |
//| The  algorithm  pre-multiplies  the  matrix  by  an  elementary  |
//| reflection transformation  which  is  given  by  column  V  and  |
//| scalar  Tau (see the description of the GenerateReflection). Not |
//| the whole matrix  but  only  a part of it is transformed (rows   |
//| from M1 to M2, columns from N1 to N2). Only the elements of this |
//| submatrix are changed.                                           |
//| Note: the matrix is multiplied by H, not by H'.   If  it  is     |
//| required  to multiply the matrix by H', it is necessary to pass  |
//| Conj(Tau) instead of Tau.                                        |
//| Input parameters:                                                |
//|     C       -   matrix to be transformed.                        |
//|     Tau     -   scalar defining transformation.                  |
//|     V       -   column defining transformation.                  |
//|                 Array whose index ranges within [1..M2-M1+1]     |
//|     M1, M2  -   range of rows to be transformed.                 |
//|     N1, N2  -   range of columns to be transformed.              |
//|     WORK    -   working array whose index goes from N1 to N2.    |
//| Output parameters:                                               |
//|     C       -   the result of multiplying the input matrix C by  |
//|                 the transformation matrix which is given by Tau  |
//|                 and V. If N1>N2 or M1>M2, C is not modified.     |
//|   -- LAPACK auxiliary routine (version 3.0) --                   |
//|      Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., |
//|      Courant Institute, Argonne National Lab, and Rice University|
//|      September 30, 1994                                          |
//+------------------------------------------------------------------+
void CComplexReflections::ComplexApplyReflectionFromTheLeft(CMatrixComplex &c,
                                                            complex tau,
                                                            complex &v[],
                                                            const int m1,
                                                            const int m2,
                                                            const int n1,
                                                            const int n2,
                                                            complex &work[])
  {
//--- check
   if(tau==0 || n1>n2 || m1>m2)
      return;
//--- create variables
   complex t=0;
   int     i=0;
   int     vm=0;
   int     i_=0;
//--- w := C^T * conj(v)
   vm=m2-m1+1;
   for(i=n1; i<=n2; i++)
      work[i]=0;
   for(i=m1; i<=m2; i++)
     {
      t=CMath::Conj(v[i+1-m1]);
      for(i_=n1; i_<=n2; i_++)
         work[i_]=work[i_]+t*c.Get(i,i_);
     }
//--- C := C - tau * v * w^T
   for(i=m1; i<=m2; i++)
     {
      t=v[i-m1+1]*tau;
      for(i_=n1; i_<=n2; i_++)
         c.Set(i,i_,c.Get(i,i_)-t*work[i_]);
     }
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CComplexReflections::ComplexApplyReflectionFromTheLeft(CMatrixComplex &c,
                                                            complex tau,
                                                            CRowComplex &v,
                                                            const int m1,
                                                            const int m2,
                                                            const int n1,
                                                            const int n2,
                                                            CRowComplex &work)
  {
//--- check
   if(tau==0 || n1>n2 || m1>m2)
      return;
//--- create variables
   complex t=0;
   int     i=0;
   int     vm=0;
   int     i_=0;
//--- w := C^T * conj(v)
   vm=m2-m1+1;
   for(i=n1; i<=n2; i++)
      work.Set(i,0.0);
   for(i=m1; i<=m2; i++)
     {
      t=CMath::Conj(v[i+1-m1]);
      for(i_=n1; i_<=n2; i_++)
         work.Set(i_,work[i_]+t*c.Get(i,i_));
     }
//--- C := C - tau * v * w^T
   for(i=m1; i<=m2; i++)
     {
      t=v[i-m1+1]*tau;
      for(i_=n1; i_<=n2; i_++)
         c.Set(i,i_,c.Get(i,i_)-t*work[i_]);
     }
  }
//+------------------------------------------------------------------+
//| Application of an elementary reflection to a rectangular matrix  |
//| of size MxN                                                      |
//| The  algorithm  post-multiplies  the  matrix  by  an elementary  |
//| reflection transformation  which  is  given  by  column  V  and  |
//| scalar  Tau (see the description  of  the  GenerateReflection).  |
//| Not the whole matrix but only a part  of  it  is  transformed    |
//| (rows from M1 to M2, columns from N1 to N2). Only the elements   |
//| of this submatrix are changed.                                   |
//| Input parameters:                                                |
//|     C       -   matrix to be transformed.                        |
//|     Tau     -   scalar defining transformation.                  |
//|     V       -   column defining transformation.                  |
//|                 Array whose index ranges within [1..N2-N1+1]     |
//|     M1, M2  -   range of rows to be transformed.                 |
//|     N1, N2  -   range of columns to be transformed.              |
//|     WORK    -   working array whose index goes from M1 to M2.    |
//| Output parameters:                                               |
//|     C       -   the result of multiplying the input matrix C by  |
//|                 the transformation matrix which is given by Tau  |
//|                 and V. If N1>N2 or M1>M2, C is not modified.     |
//|   -- LAPACK auxiliary routine (version 3.0) --                   |
//|      Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., |
//|      Courant Institute, Argonne National Lab, and Rice University|
//|      September 30, 1994                                          |
//+------------------------------------------------------------------+
void CComplexReflections::ComplexApplyReflectionFromTheRight(CMatrixComplex &c,
                                                             complex tau,
                                                             complex &v[],
                                                             const int m1,
                                                             const int m2,
                                                             const int n1,
                                                             const int n2,
                                                             complex &work[])
  {
//--- check
   if(tau==0 || n1>n2 || m1>m2)
      return;
//--- create variables
   complex t=0;
   int     i=0;
   int     vm=0;
   int     i_=0;
   int     i1_=0;
   vector<complex> tempV;
//--- w := C * v
   vm=n2-n1+1;
   for(i=m1; i<=m2; i++)
     {
      i1_=1-n1;
      t=0.0;
      //--- change values
      for(i_=n1; i_<=n2; i_++)
         t+=c.Get(i,i_)*v[i_+i1_];
      work[i]=t;
     }
//--- C := C - w * conj(v^T)
   tempV.Init(vm+1);
   for(i_=1; i_<=vm; i_++)
      tempV[i_]=CMath::Conj(v[i_]);
//--- get result
   for(i=m1; i<=m2; i++)
     {
      t=work[i]*tau;
      i1_=1-n1;
      for(i_=n1; i_<=n2; i_++)
         c.Set(i,i_,c.Get(i,i_)-t*tempV[i_+i1_]);
     }
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CComplexReflections::ComplexApplyReflectionFromTheRight(CMatrixComplex &c,
                                                             complex tau,
                                                             CRowComplex &v,
                                                             const int m1,
                                                             const int m2,
                                                             const int n1,
                                                             const int n2,
                                                             CRowComplex &work)
  {
//--- check
   if(tau==0 || n1>n2 || m1>m2)
      return;
//--- create variables
   complex t=0;
   int     i=0;
   int     vm=0;
   int     i_=0;
   int     i1_=0;
   vector<complex> tempV;
//--- w := C * v
   vm=n2-n1+1;
   for(i=m1; i<=m2; i++)
     {
      i1_=1-n1;
      t=0.0;
      //--- change values
      for(i_=n1; i_<=n2; i_++)
         t+=c.Get(i,i_)*v[i_+i1_];
      work.Set(i,t);
     }
//--- C := C - w * conj(v^T)
   tempV.Init(vm+1);
   for(i_=1; i_<=vm; i_++)
      tempV[i_]=CMath::Conj(v[i_]);
//--- get result
   for(i=m1; i<=m2; i++)
     {
      t=work[i]*tau;
      i1_=1-n1;
      for(i_=n1; i_<=n2; i_++)
         c.Set(i,i_,c.Get(i,i_)-t*tempV[i_+i1_]);
     }
  }
//+------------------------------------------------------------------+
//| Work with the symmetric matrix                                   |
//+------------------------------------------------------------------+
class CSblas
  {
public:
   static void       SymmetricMatrixVectorMultiply(const CMatrixDouble &a,const bool IsUpper,const int i1,const int i2,const double &x[],const double alpha,double &y[]);
   static void       SymmetricMatrixVectorMultiply(const CMatrixDouble &a,const bool IsUpper,const int i1,const int i2,const CRowDouble &x,const double alpha,CRowDouble &y);
   static void       SymmetricRank2Update(CMatrixDouble &a,const bool IsUpper,const int i1,const int i2,const double &x[],const double &y[],double &t[],const double alpha);
   static void       SymmetricRank2Update(CMatrixDouble &a,const bool IsUpper,const int i1,const int i2,const CRowDouble &x,const CRowDouble  &y,CRowDouble &t,const double alpha);
  };
//+------------------------------------------------------------------+
//| Multiply                                                         |
//+------------------------------------------------------------------+
void CSblas::SymmetricMatrixVectorMultiply(const CMatrixDouble &a,
                                           const bool IsUpper,
                                           const int i1,const int i2,
                                           const double &x[],
                                           const double alpha,
                                           double &y[])
  {
//--- create variables
   int    i=0;
   int    ba1=0;
   int    by1=0;
   int    by2=0;
   int    bx1=0;
   int    bx2=0;
   int    n=i2-i1+1;
   double v=0;
   int    i_=0;
   int    i1_=0;
//--- check
   if(n<=0)
      return;
//--- Let A = L + D + U, where
//---  L is strictly lower triangular (main diagonal is zero)
//---  D is diagonal
//---  U is strictly upper triangular (main diagonal is zero)
//--- A*x = L*x + D*x + U*x
//--- Calculate D*x first
   for(i=i1; i<=i2; i++)
      y[i-i1+1]=a.Get(i,i)*x[i-i1+1];
//--- Add L*x + U*x
   if(IsUpper)
     {
      for(i=i1; i<i2; i++)
        {
         //--- Add L*x to the result
         v=x[i-i1+1];
         by1=i-i1+2;
         by2=n;
         ba1=i+1;
         i1_=ba1-by1;
         for(i_=by1; i_<=by2; i_++)
            y[i_]=y[i_]+v*a.Get(i,i_+i1_);
         //--- Add U*x to the result
         bx1=by1;
         bx2=n;
         i1_=ba1-bx1;
         v=0.0;
         for(i_=bx1; i_<=bx2; i_++)
            v+=x[i_]*a.Get(i,i_+i1_);
         y[i-i1+1]=y[i-i1+1]+v;
        }
     }
   else
     {
      for(i=i1+1; i<=i2; i++)
        {
         //--- Add L*x to the result
         bx1=1;
         bx2=i-i1;
         ba1=i1;
         i1_=(ba1)-(bx1);
         v=0.0;
         for(i_=bx1; i_<=bx2; i_++)
            v+=x[i_]*a.Get(i,i_+i1_);
         y[i-i1+1]=y[i-i1+1]+v;
         //--- Add U*x to the result
         v=x[i-i1+1];
         by1=1;
         by2=bx2;
         i1_=ba1-by1;
         for(i_=by1; i_<=by2; i_++)
            y[i_]=y[i_]+v*a.Get(i,i_+i1_);
        }
     }
//--- get result
   for(i_=1; i_<=n; i_++)
      y[i_]=alpha*y[i_];
  }
//+------------------------------------------------------------------+
//| Multiply                                                         |
//+------------------------------------------------------------------+
void CSblas::SymmetricMatrixVectorMultiply(const CMatrixDouble &a,
                                           const bool IsUpper,
                                           const int i1,const int i2,
                                           const CRowDouble &x,
                                           const double alpha,
                                           CRowDouble &y)
  {
//--- create variables
   int    i=0;
   int    ba1=0;
   int    by1=0;
   int    by2=0;
   int    bx1=0;
   int    bx2=0;
   int    n=i2-i1+1;
   double v=0;
   int    i_=0;
   int    i1_=0;
//--- check
   if(n<=0)
      return;
//--- Let A = L + D + U, where
//---  L is strictly lower triangular (main diagonal is zero)
//---  D is diagonal
//---  U is strictly upper triangular (main diagonal is zero)
//--- A*x = L*x + D*x + U*x
//--- Calculate D*x first
   for(i=i1; i<=i2; i++)
      y.Set(i-i1+1,a.Get(i,i)*x[i-i1+1]);
//--- Add L*x + U*x
   if(IsUpper)
     {
      for(i=i1; i<i2; i++)
        {
         //--- Add L*x to the result
         v=x[i-i1+1];
         by1=i-i1+2;
         by2=n;
         ba1=i+1;
         i1_=ba1-by1;
         for(i_=by1; i_<=by2; i_++)
            y.Set(i_,y[i_]+v*a.Get(i,i_+i1_));
         //--- Add U*x to the result
         bx1=by1;
         bx2=n;
         i1_=ba1-bx1;
         v=0.0;
         for(i_=bx1; i_<=bx2; i_++)
            v+=x[i_]*a.Get(i,i_+i1_);
         y.Set(i-i1+1,y[i-i1+1]+v);
        }
     }
   else
     {
      for(i=i1+1; i<=i2; i++)
        {
         //--- Add L*x to the result
         bx1=1;
         bx2=i-i1;
         ba1=i1;
         i1_=(ba1)-(bx1);
         v=0.0;
         for(i_=bx1; i_<=bx2; i_++)
            v+=x[i_]*a.Get(i,i_+i1_);
         y.Set(i-i1+1,y[i-i1+1]+v);
         //--- Add U*x to the result
         v=x[i-i1+1];
         by1=1;
         by2=bx2;
         i1_=ba1-by1;
         for(i_=by1; i_<=by2; i_++)
            y.Set(i_,y[i_]+v*a.Get(i,i_+i1_));
        }
     }
//--- get result
   for(i_=1; i_<=n; i_++)
      y.Set(i_,alpha*y[i_]);
  }
//+------------------------------------------------------------------+
//| Update matrix                                                    |
//+------------------------------------------------------------------+
void CSblas::SymmetricRank2Update(CMatrixDouble &a,const bool IsUpper,
                                  const int i1,const int i2,
                                  const double &x[],const double &y[],
                                  double &t[],const double alpha)
  {
//--- create variables
   int    i=0;
   int    tp1=0;
   int    tp2=0;
   double v=0;
   int    i_=0;
   int    i1_=0;
//--- check
   if(IsUpper)
     {
      for(i=i1; i<=i2; i++)
        {
         //--- change values
         tp1=i+1-i1;
         tp2=i2-i1+1;
         v=x[tp1];
         //--- change t
         for(i_=tp1; i_<=tp2; i_++)
            t[i_]=v*y[i_];
         v=y[tp1];
         //--- change t
         for(i_=tp1; i_<=tp2; i_++)
            t[i_]=(t[i_]+v*x[i_])*alpha;
         i1_=tp1-i;
         //--- change a
         for(i_=i; i_<=i2; i_++)
            a.Set(i,i_,a.Get(i,i_)+t[i_+i1_]);
        }
     }
   else
     {
      for(i=i1; i<=i2; i++)
        {
         //--- change values
         tp1=1;
         tp2=i+1-i1;
         v=x[tp2];
         //--- change t
         for(i_=tp1; i_<=tp2; i_++)
            t[i_]=v*y[i_];
         v=y[tp2];
         //--- change t
         for(i_=tp1; i_<=tp2; i_++)
            t[i_]=(t[i_]+v*x[i_])*alpha;
         i1_=tp1-i1;
         //--- change a
         for(i_=i1; i_<=i; i_++)
            a.Set(i,i_,a.Get(i,i_)+t[i_+i1_]);
        }
     }
  }
//+------------------------------------------------------------------+
//| Update matrix                                                    |
//+------------------------------------------------------------------+
void CSblas::SymmetricRank2Update(CMatrixDouble &a,const bool IsUpper,
                                  const int i1,const int i2,
                                  const CRowDouble &x,const CRowDouble &y,
                                  CRowDouble &t,const double alpha)
  {
//--- create variables
   int    i=0;
   int    tp1=0;
   int    tp2=0;
   double v=0;
   int    i_=0;
   int    i1_=0;
//--- check
   if(IsUpper)
     {
      for(i=i1; i<=i2; i++)
        {
         //--- change values
         tp1=i+1-i1;
         tp2=i2-i1+1;
         v=x[tp1];
         //--- change t
         for(i_=tp1; i_<=tp2; i_++)
            t.Set(i_,v*y[i_]);
         v=y[tp1];
         //--- change t
         for(i_=tp1; i_<=tp2; i_++)
            t.Set(i_,(t[i_]+v*x[i_])*alpha);
         i1_=tp1-i;
         //--- change a
         for(i_=i; i_<=i2; i_++)
            a.Set(i,i_,a.Get(i,i_)+t[i_+i1_]);
        }
     }
   else
     {
      for(i=i1; i<=i2; i++)
        {
         //--- change values
         tp1=1;
         tp2=i+1-i1;
         v=x[tp2];
         //--- change t
         for(i_=tp1; i_<=tp2; i_++)
            t.Set(i_,v*y[i_]);
         v=y[tp2];
         //--- change t
         for(i_=tp1; i_<=tp2; i_++)
            t.Set(i_,(t[i_]+v*x[i_])*alpha);
         i1_=tp1-i1;
         //--- change a
         for(i_=i1; i_<=i; i_++)
            a.Set(i,i_,a.Get(i,i_)+t[i_+i1_]);
        }
     }
  }
//+------------------------------------------------------------------+
//| Rotations                                                        |
//+------------------------------------------------------------------+
class CRotations
  {
public:
   static void       ApplyRotationsFromTheLeft(const bool isforward,const int m1,const int m2,const int n1,const int n2,double &c[],double &s[],CMatrixDouble &a,double &work[]);
   static void       ApplyRotationsFromTheLeft(const bool isforward,const int m1,const int m2,const int n1,const int n2,CRowDouble &c,CRowDouble &s,CMatrixDouble &a,CRowDouble &work);
   static void       ApplyRotationsFromTheRight(const bool isforward,const int m1,const int m2,const int n1,const int n2,double &c[],double &s[],CMatrixDouble &a,double &work[]);
   static void       ApplyRotationsFromTheRight(const bool isforward,const int m1,const int m2,const int n1,const int n2,CRowDouble &c,CRowDouble &s,CMatrixDouble &a,CRowDouble &work);
   static void       GenerateRotation(const double f,const double g,double &cs,double &sn,double &r);
  };
//+------------------------------------------------------------------+
//| Application of a sequence of  elementary rotations to a matrix   |
//| The algorithm pre-multiplies the matrix by a sequence of rotation|
//| transformations which is given by arrays C and S. Depending on   |
//| the value of the IsForward parameter either 1 and 2, 3 and 4 and |
//| so on (if IsForward=true) rows are rotated, or the rows N and    |
//| N-1, N-2 and N-3 and so on, are rotated.                         |
//| Not the whole matrix but only a part of it is transformed (rows  |
//| from M1 to M2, columns from N1 to N2). Only the elements of this |
//| submatrix are changed.                                           |
//| Input parameters:                                                |
//|     IsForward   -   the sequence of the rotation application.    |
//|     M1,M2       -   the range of rows to be transformed.         |
//|     N1, N2      -   the range of columns to be transformed.      |
//|     C,S         -   transformation coefficients.                 |
//|                     Array whose index ranges within [1..M2-M1].  |
//|     A           -   processed matrix.                            |
//|     WORK        -   working array whose index ranges within      |
//|                     [N1..N2].                                    |
//| Output parameters:                                               |
//|     A           -   transformed matrix.                          |
//| Utility subroutine.                                              |
//+------------------------------------------------------------------+
void CRotations::ApplyRotationsFromTheLeft(const bool isforward,
                                           const int m1,const int m2,
                                           const int n1,const int n2,
                                           double &c[],double &s[],
                                           CMatrixDouble &a,double &work[])
  {
   CRowDouble C=c;
   CRowDouble S=s;
   CRowDouble Work=work;
   ApplyRotationsFromTheLeft(isforward,m1,m2,n1,n2,C,S,a,Work);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CRotations::ApplyRotationsFromTheLeft(const bool isforward,
                                           const int m1,const int m2,
                                           const int n1,const int n2,
                                           CRowDouble &c,CRowDouble &s,
                                           CMatrixDouble &a,CRowDouble &work)
  {
//--- create variables
   int    j=0;
   int    jp1=0;
   double ctemp=0;
   double stemp=0;
   double temp=0;
   int    i_=0;
//--- check
   if(m1>m2 || n1>n2)
      return;
//--- Form  P * A
   if(isforward)
     {
      //--- check
      if(n1!=n2)
        {
         //--- Common case: N1<>N2
         for(j=m1; j<m2; j++)
           {
            ctemp=c[j-m1+1];
            stemp=s[j-m1+1];
            //--- check
            if(ctemp!=1.0 || stemp!=0.0)
              {
               jp1=j+1;
               //--- prepare array
               for(i_=n1; i_<=n2; i_++)
                  work.Set(i_,(ctemp*a.Get(jp1,i_)-stemp*a.Get(j,i_)));
               //--- get result
               for(i_=n1; i_<=n2; i_++)
                  a.Set(j,i_,(ctemp*a.Get(j,i_)+stemp*a[jp1][i_]));
               for(i_=n1; i_<=n2; i_++)
                  a.Set(jp1,i_,work[i_]);
              }
           }
        }
      else
        {
         //--- Special case: N1=N2
         for(j=m1; j<m2; j++)
           {
            ctemp=c[j-m1+1];
            stemp=s[j-m1+1];
            //--- check
            if(ctemp!=1.0 || stemp!=0.0)
              {
               temp=a.Get(j+1,n1);
               //--- get result
               a.Set(j+1,n1,ctemp*temp-stemp*a.Get(j,n1));
               a.Set(j,n1,stemp*temp+ctemp*a.Get(j,n1));
              }
           }
        }
     }
   else
     {
      if(n1!=n2)
        {
         //--- Common case: N1<>N2
         for(j=m2-1; j>=m1; j--)
           {
            ctemp=c[j-m1+1];
            stemp=s[j-m1+1];
            //--- check
            if(ctemp!=1.0 || stemp!=0.0)
              {
               jp1=j+1;
               //--- prepare array
               for(i_=n1; i_<=n2; i_++)
                  work.Set(i_,(ctemp*a.Get(jp1,i_)-stemp*a.Get(j,i_)));
               //--- get result
               for(i_=n1; i_<=n2; i_++)
                  a.Set(j,i_,(ctemp*a.Get(j,i_)+stemp*a.Get(jp1,i_)));
               for(i_=n1; i_<=n2; i_++)
                  a.Set(jp1,i_,work[i_]);
              }
           }
        }
      else
        {
         //--- Special case: N1=N2
         for(j=m2-1; j>=m1; j--)
           {
            ctemp=c[j-m1+1];
            stemp=s[j-m1+1];
            //--- check
            if(ctemp!=1.0 || stemp!=0.0)
              {
               temp=a[j+1][n1];
               //--- get result
               a.Set(j+1,n1,ctemp*temp-stemp*a.Get(j,n1));
               a.Set(j,n1,stemp*temp+ctemp*a.Get(j,n1));
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+
//| Application of a sequence of  elementary rotations to a matrix   |
//| The algorithm post-multiplies the matrix by a sequence of        |
//| rotation transformations which is given by arrays C and S.       |
//| Depending on the value of the IsForward parameter either 1 and 2,|
//| 3 and 4 and so on (if IsForward=true) rows are rotated, or the   |
//| rows N and N-1, N-2 and N-3 and so on are rotated.               |
//| Not the whole matrix but only a part of it is transformed (rows  |
//| from M1 to M2, columns from N1 to N2). Only the elements of this |
//| submatrix are changed.                                           |
//| Input parameters:                                                |
//|     IsForward   -   the sequence of the rotation application.    |
//|     M1,M2       -   the range of rows to be transformed.         |
//|     N1, N2      -   the range of columns to be transformed.      |
//|     C,S         -   transformation coefficients.                 |
//|                     Array whose index ranges within [1..N2-N1].  |
//|     A           -   processed matrix.                            |
//|     WORK        -   working array whose index ranges within      |
//|                     [M1..M2].                                    |
//| Output parameters:                                               |
//|     A           -   transformed matrix.                          |
//| Utility subroutine.                                              |
//+------------------------------------------------------------------+
void CRotations::ApplyRotationsFromTheRight(const bool isforward,
                                            const int m1,const int m2,
                                            const int n1,const int n2,
                                            double &c[],double &s[],
                                            CMatrixDouble &a,
                                            double &work[])
  {
   CRowDouble C=c;
   CRowDouble S=s;
   CRowDouble Work=work;
   ApplyRotationsFromTheRight(isforward,m1,m2,n1,n2,C,S,a,Work);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CRotations::ApplyRotationsFromTheRight(const bool isforward,
                                            const int m1,const int m2,
                                            const int n1,const int n2,
                                            CRowDouble &c,CRowDouble &s,
                                            CMatrixDouble &a,
                                            CRowDouble &work)
  {
//--- create variables
   int    j=0;
   int    jp1=0;
   double ctemp=0;
   double stemp=0;
   double temp=0;
   int    i_=0;
//--- Form A * P'
   if(isforward)
     {
      //--- check
      if(m1!=m2)
        {
         //--- Common case: M1<>M2
         for(j=n1; j<=n2-1; j++)
           {
            ctemp=c[j-n1+1];
            stemp=s[j-n1+1];
            //--- check
            if(ctemp!=1.0 || stemp!=0.0)
              {
               jp1=j+1;
               //--- prepare array
               for(i_=m1; i_<=m2; i_++)
                  work.Set(i_,(ctemp*a.Get(i_,jp1)-stemp*a.Get(i_,j)));
               //--- get result
               for(i_=m1; i_<=m2; i_++)
                  a.Set(i_,j,(ctemp*a.Get(i_,j)+stemp*a.Get(i_,jp1)));
               for(i_=m1; i_<=m2; i_++)
                  a.Set(i_,jp1,work[i_]);
              }
           }
        }
      else
        {
         //--- Special case: M1=M2
         for(j=n1; j<=n2-1; j++)
           {
            ctemp=c[j-n1+1];
            stemp=s[j-n1+1];
            //--- check
            if(ctemp!=1.0 || stemp!=0.0)
              {
               temp=a[m1][j+1];
               //--- get result
               a.Set(m1,j+1,ctemp*temp-stemp*a.Get(m1,j));
               a.Set(m1,j,stemp*temp+ctemp*a.Get(m1,j));
              }
           }
        }
     }
   else
     {
      //--- check
      if(m1!=m2)
        {
         //--- Common case: M1<>M2
         for(j=n2-1; j>=n1; j--)
           {
            ctemp=c[j-n1+1];
            stemp=s[j-n1+1];
            //--- check
            if(ctemp!=1.0 || stemp!=0.0)
              {
               jp1=j+1;
               //--- prepare array
               for(i_=m1; i_<=m2; i_++)
                  work.Set(i_,(ctemp*a.Get(i_,jp1)-stemp*a.Get(i_,j)));
               //--- get result
               for(i_=m1; i_<=m2; i_++)
                  a.Set(i_,j,(ctemp*a.Get(i_,j)+stemp*a.Get(i_,jp1)));
               for(i_=m1; i_<=m2; i_++)
                  a.Set(i_,jp1,work[i_]);
              }
           }
        }
      else
        {
         //--- Special case: M1=M2
         for(j=n2-1; j>=n1; j--)
           {
            ctemp=c[j-n1+1];
            stemp=s[j-n1+1];
            //--- check
            if(ctemp!=1.0 || stemp!=0.0)
              {
               temp=a[m1][j+1];
               //--- get result
               a.Set(m1,j+1,ctemp*temp-stemp*a.Get(m1,j));
               a.Set(m1,j,stemp*temp+ctemp*a.Get(m1,j));
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+
//| The subroutine generates the elementary rotation, so that:       |
//| [  CS  SN  ]  .  [ F ]  =  [ R ]                                 |
//| [ -SN  CS  ]     [ G ]     [ 0 ]                                 |
//| CS**2 + SN**2 = 1                                                |
//+------------------------------------------------------------------+
void CRotations::GenerateRotation(const double f,const double g,
                                  double &cs,double &sn,double &r)
  {
//--- create variables
   double f1=0;
   double g1=0;
//--- check
   if(g==0.0)
     {
      //--- get result
      cs=1;
      sn=0;
      r=f;
     }
   else
     {
      //--- check
      if(f==0.0)
        {
         //--- get result
         cs=0;
         sn=1;
         r=g;
        }
      else
        {
         f1=f;
         g1=g;
         //--- check
         if(MathAbs(f1)>MathAbs(g1))
            r=MathAbs(f1)*MathSqrt(1+CMath::Sqr(g1/f1));
         else
            r=MathAbs(g1)*MathSqrt(1+CMath::Sqr(f1/g1));
         cs=f1/r;
         sn=g1/r;
         //--- check
         if(MathAbs(f)>MathAbs(g) && cs<0.0)
           {
            //--- get result
            cs=-cs;
            sn=-sn;
            r=-r;
           }
        }
     }
  }
//+------------------------------------------------------------------+
//| Safe solutions for tridiagonal linear matrix                     |
//+------------------------------------------------------------------+
class CTrLinSolve
  {
public:
   static void       RMatrixTrSafeSolve(CMatrixDouble &a,const int n,double &x[],double &s,const bool IsUpper,const bool IsTrans,const bool IsUnit);
   static void       RMatrixTrSafeSolve(CMatrixDouble &a,const int n,CRowDouble &x,double &s,const bool IsUpper,const bool IsTrans,const bool IsUnit);
   static void       SafeSolveTriangular(CMatrixDouble &a,const int n,double &x[],double &s,const bool IsUpper,const bool IsTrans,const bool IsUnit,const bool normin,double &cnorm[]);
   static void       SafeSolveTriangular(CMatrixDouble &a,const int n,CRowDouble &x,double &s,const bool IsUpper,const bool IsTrans,const bool IsUnit,const bool normin,CRowDouble &cnorm);
  };
//+------------------------------------------------------------------+
//| Utility subroutine performing the "safe" solution of system of   |
//| linear equations with triangular coefficient matrices.           |
//| The subroutine uses scaling and solves the scaled system A*x=s*b |
//| (where  s is  a  scalar  value)  instead  of  A*x=b,  choosing   |
//| s  so  that x can be represented by a floating-point number. The |
//| closer the system  gets  to  a  singular, the less s is. If the  |
//| system is singular, s=0 and x contains the non-trivial solution  |
//| of equation A*x=0.                                               |
//| The feature of an algorithm is that it could not cause an        |
//| overflow  or  a division by zero regardless of the matrix used   |
//| as the input.                                                    |
//| The algorithm can solve systems of equations with  upper/lower   |
//| triangular matrices,  with/without unit diagonal, and systems of |
//| type A*x=b or A'*x=b (where A' is a transposed matrix A).        |
//| Input parameters:                                                |
//|     A       -   system matrix. Array whose indexes range within  |
//|                 [0..N-1, 0..N-1].                                |
//|     N       -   size of matrix A.                                |
//|     X       -   right-hand member of a system.                   |
//|                 Array whose index ranges within [0..N-1].        |
//|     IsUpper -   matrix type. If it is True, the system matrix is |
//|                 the upper triangular and is located in  the      |
//|                 corresponding  part  of matrix A.                |
//|     Trans   -   problem type. If it is True, the problem to be   |
//|                 solved  is A'*x=b, otherwise it is A*x=b.        |
//|     IsUnit  -   matrix type. If it is True, the system matrix has|
//|                 a  unit diagonal (the elements on the main       |
//|                 diagonal are  not  used in the calculation       |
//|                 process), otherwise the matrix is considered to  |
//|                 be a general triangular matrix.                  |
//| Output parameters:                                               |
//|     X       -   solution. Array whose index ranges within        |
//|                 [0..N-1].                                        |
//|     S       -   scaling factor.                                  |
//|   -- LAPACK auxiliary routine (version 3.0) --                   |
//|      Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., |
//|      Courant Institute, Argonne National Lab, and Rice University|
//|      June 30, 1992                                               |
//+------------------------------------------------------------------+
void CTrLinSolve::RMatrixTrSafeSolve(CMatrixDouble &a,const int n,
                                     double &x[],double &s,
                                     const bool IsUpper,
                                     const bool IsTrans,
                                     const bool IsUnit)
  {
   CRowDouble X=x;
   RMatrixTrSafeSolve(a,n,X,s,IsUpper,IsTrans,IsUnit);
   X.ToArray(x);
  }
//+------------------------------------------------------------------+
//| Same                                                             |
//+------------------------------------------------------------------+
void CTrLinSolve::RMatrixTrSafeSolve(CMatrixDouble &a,const int n,
                                     CRowDouble &x,double &s,
                                     const bool IsUpper,
                                     const bool IsTrans,
                                     const bool IsUnit)
  {
//--- create variables
   bool normin;
   int  i=0;
   int  i_=0;
   int  i1_=0;
//--- create arrays
   CRowDouble cnorm;
   CRowDouble x1;
//--- create matrix
   CMatrixDouble a1;
//--- initialization
   s=0;
//--- From 0-based to 1-based
   normin=false;
//--- allocation
   a1.Resize(n+1,n+1);
   x1.Resize(n+1);
   i1_=-1;
   for(i=1; i<=n; i++)
     {
      for(i_=1; i_<=n; i_++)
         a1.Set(i,i_,a.Get(i-1,i_+i1_));
      x1.Set(i,x[i+i1_]);
     }
//--- Solve 1-based
   SafeSolveTriangular(a1,n,x1,s,IsUpper,IsTrans,IsUnit,normin,cnorm);
//--- From 1-based to 0-based
   i1_=1;
   for(i_=0; i_<n; i_++)
      x.Set(i_,x1[i_+i1_]);
  }
//+------------------------------------------------------------------+
//| Obsolete 1-based subroutine.                                     |
//| See RMatrixTRSafeSolve for 0-based replacement.                  |
//+------------------------------------------------------------------+
void CTrLinSolve::SafeSolveTriangular(CMatrixDouble &a,const int n,
                                      double &x[],double &s,
                                      const bool IsUpper,
                                      const bool IsTrans,
                                      const bool IsUnit,
                                      const bool normin,
                                      double &cnorm[])
  {
   CRowDouble X=x;
   CRowDouble CNorm=cnorm;
   SafeSolveTriangular(a,n,X,s,IsUpper,IsTrans,IsUnit,normin,CNorm);
   X.ToArray(x);
   CNorm.ToArray(cnorm);
  }
//+------------------------------------------------------------------+
//| Obsolete 1-based subroutine.                                     |
//| See RMatrixTRSafeSolve for 0-based replacement.                  |
//+------------------------------------------------------------------+
void CTrLinSolve::SafeSolveTriangular(CMatrixDouble &a,const int n,
                                      CRowDouble &x,double &s,
                                      const bool IsUpper,
                                      const bool IsTrans,
                                      const bool IsUnit,
                                      const bool normin,
                                      CRowDouble &cnorm)
  {
//--- create variables
   int    i=0;
   int    imax=0;
   int    j=0;
   int    jfirst=0;
   int    jinc=0;
   int    jlast=0;
   int    jm1=0;
   int    jp1=0;
   int    ip1=0;
   int    im1=0;
   int    k=0;
   int    flg=0;
   double v=0;
   double vd=0;
   double bignum=0;
   double grow=0;
   double rec=0;
   double smlnum=0;
   double sumj=0;
   double tjj=0;
   double tjjs=0;
   double tmax=0;
   double tscal=0;
   double uscal=0;
   double xbnd=0;
   double xj=0;
   double xmax=0;
   bool   notran;
   bool   upper;
   bool   nounit;
   int    i_=0;
//--- initialization
   s=0;
   upper=IsUpper;
   notran=!IsTrans;
   nounit=!IsUnit;
//--- these initializers are not really necessary,
//--- but without them compiler complains about uninitialized locals
   tjjs=0;
//--- Quick return if possible
   if(n==0)
      return;
//--- Determine machine dependent parameters to control overflow.
   smlnum=CMath::m_minrealnumber/(CMath::m_machineepsilon*2);
   bignum=1/smlnum;
   s=1;
//--- check
   if(!normin)
     {
      cnorm.Resize(n+1);
      //--- Compute the 1-norm of each column,not including the diagonal.
      if(upper)
        {
         //--- A is upper triangular.
         for(j=1; j<=n; j++)
           {
            v=0;
            for(k=1; k<j; k++)
               v=v+MathAbs(a.Get(k,j));
            cnorm.Set(j,v);
           }
        }
      else
        {
         //--- A is lower triangular.
         for(j=1; j<n; j++)
           {
            v=0;
            for(k=j+1; k<=n; k++)
               v=v+MathAbs(a.Get(k,j));
            cnorm.Set(j,v);
           }
         cnorm.Set(n,0);
        }
     }
//--- Scale the column norms by TSCAL if the maximum element in CNORM is
//--- greater than BIGNUM.
   imax=1;
   for(k=2; k<=n; k++)
     {
      //--- check
      if(cnorm[k]>cnorm[imax])
         imax=k;
     }
   tmax=cnorm[imax];
//--- check
   if(tmax<=bignum)
      tscal=1;
   else
     {
      tscal=1/(smlnum*tmax);
      for(i_=1; i_<=n; i_++)
         cnorm.Set(i_,tscal*cnorm[i_]);
     }
//--- Compute a bound on the computed solution vector to see if the
//--- Level 2 BLAS routine DTRSV can be used.
   j=1;
   for(k=2; k<=n; k++)
     {
      //--- check
      if(MathAbs(x[k])>MathAbs(x[j]))
         j=k;
     }
//--- change values
   xmax=MathAbs(x[j]);
   xbnd=xmax;
//--- check
   if(notran)
     {
      //--- Compute the growth in A * x=b.
      if(upper)
        {
         jfirst=n;
         jlast=1;
         jinc=-1;
        }
      else
        {
         jfirst=1;
         jlast=n;
         jinc=1;
        }
      //--- check
      if(tscal!=1.0)
         grow=0;
      else
        {
         //--- check
         if(nounit)
           {
            //--- A is non-unit triangular.
            //--- Compute GROW=1/G(j) and XBND=1/M(j).
            //--- Initially,G(0)=max{x(i),i=1,...,n}.
            grow=1/MathMax(xbnd,smlnum);
            xbnd=grow;
            j=jfirst;
            while((jinc>0 && j<=jlast) || (jinc<0 && j>=jlast))
              {
               //--- Exit the loop if the growth factor is too small.
               if(grow<=smlnum)
                  break;
               //--- M(j)=G(j-1) / abs(A(j,j))
               tjj=MathAbs(a.Get(j,j));
               xbnd=MathMin(xbnd,MathMin(1,tjj)*grow);
               //--- check
               if(tjj+cnorm[j]>=smlnum)
                 {
                  //--- G(j)=G(j-1)*( 1 + CNORM(j) / abs(A(j,j)) )
                  grow*=(tjj/(tjj+cnorm[j]));
                 }
               else
                 {
                  //--- G(j) could overflow,set GROW to 0.
                  grow=0;
                 }
               //--- check
               if(j==jlast)
                  grow=xbnd;
               j=j+jinc;
              }
           }
         else
           {
            //--- A is unit triangular.
            //--- Compute GROW=1/G(j), where G(0)=max{x(i), i=1,...,n}.
            grow=MathMin(1,1/MathMax(xbnd,smlnum));
            j=jfirst;
            while((jinc>0 && j<=jlast) || (jinc<0 && j>=jlast))
              {
               //--- Exit the loop if the growth factor is too small.
               if(grow<=smlnum)
                  break;
               //--- G(j) = G(j-1)*( 1 + CNORM(j) )
               grow/=(1+cnorm[j]);
               j=j+jinc;
              }
           }
        }
     }
   else
     {
      //--- Compute the growth in A' * x = b.
      if(upper)
        {
         jfirst=1;
         jlast=n;
         jinc=1;
        }
      else
        {
         jfirst=n;
         jlast=1;
         jinc=-1;
        }
      //--- check
      if(tscal!=1.0)
         grow=0;
      else
        {
         //--- check
         if(nounit)
           {
            //--- A is non-unit triangular.
            //--- Compute GROW=1/G(j) and XBND=1/M(j).
            //--- Initially, M(0)=max{x(i), i=1,...,n}.
            grow=1/MathMax(xbnd,smlnum);
            xbnd=grow;
            j=jfirst;
            while((jinc>0 && j<=jlast) || (jinc<0 && j>=jlast))
              {
               //--- Exit the loop if the growth factor is too small.
               if(grow<=smlnum)
                  break;
               //--- G(j) = max( G(j-1), M(j-1)*( 1 + CNORM(j) ) )
               xj=1+cnorm[j];
               grow=MathMin(grow,xbnd/xj);
               //--- M(j)=M(j-1)*( 1 + CNORM(j) ) / abs(A(j,j))
               tjj=MathAbs(a.Get(j,j));
               //--- check
               if(xj>tjj)
                  xbnd=xbnd*(tjj/xj);
               //--- check
               if(j==jlast)
                  grow=MathMin(grow,xbnd);
               j=j+jinc;
              }
           }
         else
           {
            //--- A is unit triangular.
            //--- Compute GROW=1/G(j), where G(0)=max{x(i), i=1,...,n}.
            grow=MathMin(1,1/MathMax(xbnd,smlnum));
            j=jfirst;
            while((jinc>0 && j<=jlast) || (jinc<0 && j>=jlast))
              {
               //--- Exit the loop if the growth factor is too small.
               if(grow<=smlnum)
                  break;
               //--- G(j)=( 1 + CNORM(j) )*G(j-1)
               xj=1+cnorm[j];
               grow=grow/xj;
               j=j+jinc;
              }
           }
        }
     }
   if(grow*tscal>smlnum)
     {
      //--- Use the Level 2 BLAS solve if the reciprocal of the bound on
      //--- elements of X is not too small.
      if((upper && notran) || (!upper && !notran))
        {
         //--- check
         if(nounit)
            vd=a.Get(n,n);
         else
            vd=1;
         x.Set(n,x[n]/vd);
         for(i=n-1; i>=1; i--)
           {
            ip1=i+1;
            //--- check
            if(upper)
              {
               v=0.0;
               for(i_=ip1; i_<=n; i_++)
                  v+=a.Get(i,i_)*x[i_];
              }
            else
              {
               v=0.0;
               for(i_=ip1; i_<=n; i_++)
                  v+=a.Get(i_,i)*x[i_];
              }
            //--- check
            if(nounit)
               vd=a.Get(i,i);
            else
               vd=1;
            x.Set(i,(x[i]-v)/vd);
           }
        }
      else
        {
         //--- check
         if(nounit)
            vd=a.Get(1,1);
         else
            vd=1;
         x.Set(1,x[1]/vd);
         for(i=2; i<=n; i++)
           {
            im1=i-1;
            //--- check
            if(upper)
              {
               v=0.0;
               for(i_=1; i_<=im1; i_++)
                  v+=a.Get(i_,i)*x[i_];
              }
            else
              {
               v=0.0;
               for(i_=1; i_<=im1; i_++)
                  v+=a.Get(i,i_)*x[i_];
              }
            //--- check
            if(nounit)
               vd=a.Get(i,i);
            else
               vd=1;
            x.Set(i,(x[i]-v)/vd);
           }
        }
     }
   else
     {
      //--- Use a Level 1 BLAS solve, scaling intermediate results.
      if(xmax>bignum)
        {
         //--- Scale X so that its components are less than or equal to
         //--- BIGNUM in absolute value.
         s=bignum/xmax;
         for(i_=1; i_<=n; i_++)
            x.Set(i_,s*x[i_]);
         xmax=bignum;
        }
      //--- check
      if(notran)
        {
         //--- Solve A * x = b
         j=jfirst;
         while((jinc>0 && j<=jlast) || (jinc<0 && j>=jlast))
           {
            //--- Compute x(j)=b(j) / A(j,j), scaling x if necessary.
            xj=MathAbs(x[j]);
            flg=0;
            //--- check
            if(nounit)
               tjjs=a.Get(j,j)*tscal;
            else
              {
               tjjs=tscal;
               //--- check
               if(tscal==1.0)
                  flg=100;
              }
            //--- check
            if(flg!=100)
              {
               tjj=MathAbs(tjjs);
               if(tjj>smlnum)
                 {
                  //--- abs(A(j,j)) > SMLNUM:
                  if(tjj<1.0)
                    {
                     //--- check
                     if(xj>(tjj*bignum))
                       {
                        //--- Scale x by 1/b(j).
                        rec=1/xj;
                        for(i_=1; i_<=n; i_++)
                           x.Set(i_,(rec*x[i_]));
                        s*=rec;
                        xmax*=rec;
                       }
                    }
                  x.Set(j,x[j]/tjjs);
                  xj=MathAbs(x[j]);
                 }
               else
                 {
                  //--- check
                  if(tjj>0.0)
                    {
                     //--- 0 < abs(A(j,j)) <=SMLNUM:
                     if(xj>(tjj*bignum))
                       {
                        //--- Scale x by (1/abs(x(j)))*abs(A(j,j))*BIGNUM
                        //--- to avoid overflow when dividing by A(j,j).
                        rec=tjj*bignum/xj;
                        //--- check
                        if(cnorm[j]>1.0)
                          {
                           //--- Scale by 1/CNORM(j) to avoid overflow when
                           //--- multiplying x(j) times column j.
                           rec=rec/cnorm[j];
                          }
                        for(i_=1; i_<=n; i_++)
                           x.Set(i_,rec*x[i_]);
                        s*=rec;
                        xmax*=rec;
                       }
                     x.Set(j,x[j]/tjjs);
                     xj=MathAbs(x[j]);
                    }
                  else
                    {
                     //--- A(j,j) = 0:  Set x(1:n) = 0, x(j) = 1, and
                     //--- scale = 0, and compute a solution to A*x = 0.
                     for(i=1; i<=n; i++)
                        x.Set(i,0);
                     //--- change values
                     x.Set(j,1);
                     xj=1;
                     s=0;
                     xmax=0;
                    }
                 }
              }
            //--- Scale x if necessary to avoid overflow when adding a
            //--- multiple of column j of A.
            if(xj>1.0)
              {
               rec=1/xj;
               //--- check
               if(cnorm[j]>(bignum-xmax)*rec)
                 {
                  //--- Scale x by 1/(2*abs(x(j))).
                  rec*=0.5;
                  for(i_=1; i_<=n; i_++)
                     x.Set(i_,(rec*x[i_]));
                  s*=rec;
                 }
              }
            else
              {
               //--- check
               if(xj*cnorm[j]>bignum-xmax)
                 {
                  //--- Scale x by 1/2.
                  for(i_=1; i_<=n; i_++)
                     x.Set(i_,(0.5*x[i_]));
                  s*=0.5;
                 }
              }
            //--- check
            if(upper)
              {
               //--- check
               if(j>1)
                 {
                  //--- Compute the update
                  //--- x(1:j-1) := x(1:j-1) - x(j) * A(1:j-1,j)
                  v=x[j]*tscal;
                  jm1=j-1;
                  //--- change x
                  for(i_=1; i_<=jm1; i_++)
                     x.Set(i_,x[i_]-v*a.Get(i_,j));
                  i=1;
                  for(k=2; k<j; k++)
                    {
                     //--- check
                     if(MathAbs(x[k])>MathAbs(x[i]))
                        i=k;
                    }
                  xmax=MathAbs(x[i]);
                 }
              }
            else
              {
               //--- check
               if(j<n)
                 {
                  //--- Compute the update
                  //--- x(j+1:n) := x(j+1:n) - x(j) * A(j+1:n,j)
                  jp1=j+1;
                  v=x[j]*tscal;
                  //--- change x
                  for(i_=jp1; i_<=n; i_++)
                     x.Set(i_,x[i_]-v*a.Get(i_,j));
                  i=j+1;
                  for(k=j+2; k<=n; k++)
                    {
                     //--- check
                     if(MathAbs(x[k])>MathAbs(x[i]))
                        i=k;
                    }
                  xmax=MathAbs(x[i]);
                 }
              }
            j=j+jinc;
           }
        }
      else
        {
         //--- Solve A' * x = b
         j=jfirst;
         while((jinc>0 && j<=jlast) || (jinc<0 && j>=jlast))
           {
            //--- Compute x(j) = b(j) - sum A(k,j)*x(k).
            //--- k<>j
            xj=MathAbs(x[j]);
            uscal=tscal;
            rec=1/MathMax(xmax,1);
            //--- check
            if(cnorm[j]>(bignum-xj)*rec)
              {
               //--- If x(j) could overflow,scale x by 1/(2*XMAX).
               rec=rec*0.5;
               //--- check
               if(nounit)
                  tjjs=a.Get(j,j)*tscal;
               else
                  tjjs=tscal;
               tjj=MathAbs(tjjs);
               //--- check
               if(tjj>1.0)
                 {
                  //--- Divide by A(j,j) when scaling x if A(j,j) > 1.
                  rec=MathMin(1,rec*tjj);
                  uscal=uscal/tjjs;
                 }
               //--- check
               if(rec<1.0)
                 {
                  for(i_=1; i_<=n; i_++)
                     x.Set(i_,rec*x[i_]);
                  s*=rec;
                  xmax*=rec;
                 }
              }
            sumj=0;
            //--- check
            if(uscal==1.0)
              {
               //--- If the scaling needed for A in the dot product is 1,
               //--- call DDOT to perform the dot product.
               if(upper)
                 {
                  //--- check
                  if(j>1)
                    {
                     jm1=j-1;
                     sumj=0.0;
                     for(i_=1; i_<=jm1; i_++)
                        sumj+=a.Get(i_,j)*x[i_];
                    }
                  else
                     sumj=0;
                 }
               else
                 {
                  //--- check
                  if(j<n)
                    {
                     jp1=j+1;
                     sumj=0.0;
                     for(i_=jp1; i_<=n; i_++)
                        sumj+=a.Get(i_,j)*x[i_];
                    }
                 }
              }
            else
              {
               //--- Otherwise, use in-line code for the dot product.
               if(upper)
                 {
                  for(i=1; i<=j-1; i++)
                    {
                     v=a.Get(i,j)*uscal;
                     sumj+=v*x[i];
                    }
                 }
               else
                 {
                  //--- check
                  if(j<n)
                    {
                     for(i=j+1; i<=n; i++)
                       {
                        v=a.Get(i,j)*uscal;
                        sumj=sumj+v*x[i];
                       }
                    }
                 }
              }
            if(uscal==tscal)
              {
               //--- Compute x(j) := ( x(j) - sumj ) / A(j,j) if 1/A(j,j)
               //--- was not used to scale the dotproduct.
               x.Set(j,x[j]-sumj);
               xj=MathAbs(x[j]);
               flg=0;
               //--- check
               if(nounit)
                  tjjs=a.Get(j,j)*tscal;
               else
                 {
                  tjjs=tscal;
                  //--- check
                  if(tscal==1.0)
                     flg=150;
                 }
               //--- Compute x(j) = x(j) / A(j,j), scaling if necessary.
               if(flg!=150)
                 {
                  tjj=MathAbs(tjjs);
                  //--- check
                  if(tjj>smlnum)
                    {
                     //--- abs(A(j,j)) > SMLNUM:
                     if(tjj<1.0)
                       {
                        //--- check
                        if(xj>(double)(tjj*bignum))
                          {
                           //--- Scale X by 1/abs(x(j)).
                           rec=1/xj;
                           for(i_=1; i_<=n; i_++)
                              x.Set(i_,rec*x[i_]);
                           s*=rec;
                           xmax*=rec;
                          }
                       }
                     x.Set(j,x[j]/tjjs);
                    }
                  else
                    {
                     //--- check
                     if(tjj>0.0)
                       {
                        //--- 0 < abs(A(j,j)) <=SMLNUM:
                        if(xj>(double)(tjj*bignum))
                          {
                           //--- Scale x by (1/abs(x(j)))*abs(A(j,j))*BIGNUM.
                           rec=tjj*bignum/xj;
                           for(i_=1; i_<=n; i_++)
                              x.Set(i_,rec*x[i_]);
                           s*=rec;
                           xmax*=rec;
                          }
                        x.Set(j,x[j]/tjjs);
                       }
                     else
                       {
                        //--- A(j,j) = 0:  Set x(1:n) = 0, x(j) = 1, and
                        //--- scale = 0, and compute a solution to A'*x = 0.
                        for(i=1; i<=n; i++)
                           x.Set(i,0);
                        x.Set(j,1);
                        s=0;
                        xmax=0;
                       }
                    }
                 }
              }
            else
              {
               //--- Compute x(j) := x(j) / A(j,j)  - sumj if the dot
               //--- product has already been divided by 1/A(j,j).
               x.Set(j,x[j]/tjjs-sumj);
              }
            xmax=MathMax(xmax,MathAbs(x[j]));
            j=j+jinc;
           }
        }
      s=s/tscal;
     }
//--- Scale the column norms by 1/TSCAL for return.
   if(tscal!=1.0)
     {
      v=1/tscal;
      for(i_=1; i_<=n; i_++)
         cnorm.Set(i_,v*cnorm[i_]);
     }
  }
//+------------------------------------------------------------------+
//| Safe solvers                                                     |
//+------------------------------------------------------------------+
class CSafeSolve
  {
public:
   static bool       RMatrixScaledTrSafeSolve(CMatrixDouble &a,const double sa,const int n,double &x[],const bool IsUpper,const int trans,const bool IsUnit,const double maxgrowth);
   static bool       RMatrixScaledTrSafeSolve(CMatrixDouble &a,const double sa,const int n,CRowDouble &x,const bool IsUpper,const int trans,const bool IsUnit,const double maxgrowth);
   static bool       CMatrixScaledTrSafeSolve(CMatrixComplex &a,const double sa,const int n,complex &x[],const bool IsUpper,const int trans,const bool IsUnit,const double maxgrowth);
   static bool       CMatrixScaledTrSafeSolve(CMatrixComplex &a,const double sa,const int n,CRowComplex &x,const bool IsUpper,const int trans,const bool IsUnit,const double maxgrowth);

private:
   static bool       CBasicSolveAndUpdate(complex &alpha,complex &beta,const double lnmax,const double bnorm,const double maxgrowth,double &xnorm,complex &x);
  };
//+------------------------------------------------------------------+
//| Real implementation of CMatrixScaledTRSafeSolve                  |
//+------------------------------------------------------------------+
bool CSafeSolve::RMatrixScaledTrSafeSolve(CMatrixDouble &a,const double sa,
                                          const int n,double &x[],
                                          const bool IsUpper,const int trans,
                                          const bool IsUnit,const double maxgrowth)
  {
   CRowDouble X=x;
//--- check
   if(!RMatrixScaledTrSafeSolve(a,sa,n,X,IsUpper,trans,IsUnit,maxgrowth))
      return(false);
//--- function call
   X.ToArray(x);
   return(true);
  }
//+------------------------------------------------------------------+
//| Real implementation of CMatrixScaledTRSafeSolve                  |
//+------------------------------------------------------------------+
bool CSafeSolve::RMatrixScaledTrSafeSolve(CMatrixDouble &a,const double sa,
                                          const int n,CRowDouble &x,
                                          const bool IsUpper,const int trans,
                                          const bool IsUnit,const double maxgrowth)
  {
//--- create variables
   bool    result;
   double  lnmax=0;
   double  nrmb=0;
   double  nrmx=0;
   double  vr=0;
   complex alpha=0;
   complex beta=0;
   complex cx=0;
   int     i_=0;
   int     i=0;
//--- create array
   CRowDouble tmp;
//--- check
   if(!CAp::Assert(n>0,__FUNCTION__+": incorrect N!"))
      return(true);
//--- check
   if(!CAp::Assert(trans==0 || trans==1,__FUNCTION__+": incorrect Trans!"))
      return(false);
//--- initialization
   result=true;
   lnmax=MathLog(CMath::m_maxrealnumber);
//--- Load norms: right part and X
   tmp=x;
   if(!tmp.Resize(n))
      return(false);
   nrmb=tmp.MaxAbs();
   nrmx=0;
//--- Solve
//--- check
   if(IsUpper && trans==0)
     {
      //--- U*x = b
      for(i=n-1; i>=0; i--)
        {
         //--- Task is reduced to alpha*x[i] = beta
         if(IsUnit)
            alpha=sa;
         else
            alpha=a.Get(i,i)*sa;
         //--- check
         if(i<n-1)
           {
            //--- calculation
            vr=0.0;
            for(i_=i+1; i_<n; i_++)
               vr+=sa*a.Get(i,i_)*x[i_];
            beta=x[i]-vr;
           }
         else
            beta=x[i];
         //--- solve alpha*x[i] = beta
         result=CBasicSolveAndUpdate(alpha,beta,lnmax,nrmb,maxgrowth,nrmx,cx);
         //--- check
         if(!result)
            return(result);
         //--- change values
         x.Set(i,cx.real);
        }
      //--- return result
      return(result);
     }
//--- check
   if(!IsUpper && trans==0)
     {
      //--- L*x = b
      for(i=0; i<n; i++)
        {
         //--- Task is reduced to alpha*x[i] = beta
         if(IsUnit)
            alpha=sa;
         else
            alpha=a.Get(i,i)*sa;
         //--- check
         if(i>0)
           {
            //--- calculation
            vr=0.0;
            for(i_=0; i_<i; i_++)
               vr+=sa*a.Get(i,i_)*x[i_];
            beta=x[i]-vr;
           }
         else
            beta=x[i];
         //--- solve alpha*x[i] = beta
         result=CBasicSolveAndUpdate(alpha,beta,lnmax,nrmb,maxgrowth,nrmx,cx);
         //--- check
         if(!result)
            return(result);
         //--- change values
         x.Set(i,cx.real);
        }
      //--- return result
      return(result);
     }
//--- check
   if(IsUpper && trans==1)
     {
      //--- U^T*x = b
      for(i=0; i<n; i++)
        {
         //--- Task is reduced to alpha*x[i] = beta
         if(IsUnit)
            alpha=sa;
         else
            alpha=a.Get(i,i)*sa;
         beta=x[i];
         //--- solve alpha*x[i] = beta
         result=CBasicSolveAndUpdate(alpha,beta,lnmax,nrmb,maxgrowth,nrmx,cx);
         //--- check
         if(!result)
            return(result);
         //--- change values
         x.Set(i,cx.real);
         //--- update the rest of right part
         if(i<n-1)
           {
            vr=cx.real;
            for(i_=i+1; i_<n; i_++)
               x.Set(i_,x[i_]-vr*sa*a.Get(i,i_));
           }
        }
      //--- return result
      return(result);
     }
//--- check
   if(!IsUpper && trans==1)
     {
      //--- L^T*x = b
      for(i=n-1; i>=0; i--)
        {
         //--- Task is reduced to alpha*x[i] = beta
         if(IsUnit)
            alpha=sa;
         else
            alpha=a.Get(i,i)*sa;
         beta=x[i];
         //--- solve alpha*x[i] = beta
         result=CBasicSolveAndUpdate(alpha,beta,lnmax,nrmb,maxgrowth,nrmx,cx);
         //--- check
         if(!result)
            return(result);
         //--- change values
         x.Set(i,cx.real);
         //--- update the rest of right part
         if(i>0)
           {
            vr=cx.real;
            for(i_=0; i_<i; i_++)
               x.Set(i_,x[i_]-vr*sa*a.Get(i,i_));
           }
        }
      //--- return result
      return(result);
     }
//--- return result
   return(false);
  }
//+------------------------------------------------------------------+
//| Internal subroutine for safe solution of                         |
//|     SA*op(A)=b                                                   |
//| where A is NxN upper/lower triangular/unitriangular matrix, op(A)|
//| is either identity transform, transposition or Hermitian         |
//| transposition, SA is a scaling factor such that max(|SA*A[i,j]|) |
//| is close to 1.0 in magnutude.                                    |
//| This subroutine limits relative growth of solution (in inf-norm) |
//| by MaxGrowth, returning False if growth exceeds MaxGrowth.       |
//| Degenerate or near-degenerate matrices are handled correctly     |
//| (False is returned) as long as MaxGrowth is significantly less   |
//| than MaxRealNumber/norm(b).                                      |
//+------------------------------------------------------------------+
bool CSafeSolve::CMatrixScaledTrSafeSolve(CMatrixComplex &a,const double sa,
                                          const int n,complex &x[],
                                          const bool IsUpper,const int trans,
                                          const bool IsUnit,const double maxgrowth)
  {
   CRowComplex X=x;
//--- check
   if(!CMatrixScaledTrSafeSolve(a,sa,n,X,IsUpper,trans,IsUnit,maxgrowth))
      return(false);
//--- function call
   X.ToArray(x);
   return(true);
  }
//+------------------------------------------------------------------+
//| Same                                                             |
//+------------------------------------------------------------------+
bool CSafeSolve::CMatrixScaledTrSafeSolve(CMatrixComplex &a,const double sa,
                                          const int n,CRowComplex &x,
                                          const bool IsUpper,const int trans,
                                          const bool IsUnit,const double maxgrowth)
  {
//--- create variables
   complex Csa;
   bool    result=true;
   double  lnmax=MathLog(CMath::m_maxrealnumber);
   double  nrmb=0;
   double  nrmx=0;
   complex alpha=0;
   complex beta=0;
   complex vc=0;
   int     i=0;
   int     i_=0;
//--- create array
   CRowComplex tmp;
//--- check
   if(!CAp::Assert(n>0,__FUNCTION__+": incorrect N!"))
      return(true);
//--- check
   if(!CAp::Assert((trans==0 || trans==1) || trans==2,__FUNCTION__+": incorrect Trans!"))
      return(false);
//--- Load norms: right part and X
   nrmb=0;
   for(i=0; i<n; i++)
      nrmb=MathMax(nrmb,CMath::AbsComplex(x[i]));
   nrmx=0;
//--- Solve
   tmp.Resize(n);
//--- check
   if(IsUpper && trans==0)
     {
      //--- U*x = b
      for(i=n-1; i>=0; i--)
        {
         //--- Task is reduced to alpha*x[i] = beta
         if(IsUnit)
            alpha=sa;
         else
            alpha=a.Get(i,i)*sa;
         //--- check
         if(i<n-1)
           {
            //--- calculation
            vc=0.0;
            for(i_=i+1; i_<n; i_++)
               vc+=sa*a.Get(i,i_)*x[i_];
            beta=x[i]-vc;
           }
         else
            beta=x[i];
         //--- solve alpha*x[i] = beta
         result=CBasicSolveAndUpdate(alpha,beta,lnmax,nrmb,maxgrowth,nrmx,vc);
         //--- check
         if(!result)
            return(result);
         x.Set(i,vc);
        }
      //--- return result
      return(result);
     }
//--- check
   if(!IsUpper && trans==0)
     {
      //--- L*x = b
      for(i=0; i<n; i++)
        {
         //--- Task is reduced to alpha*x[i] = beta
         if(IsUnit)
            alpha=sa;
         else
            alpha=a.Get(i,i)*sa;
         //--- check
         if(i>0)
           {
            //--- calculation
            vc=0.0;
            for(i_=0; i_<i; i_++)
               vc+=sa*a.Get(i,i_)*x[i_];
            beta=x[i]-vc;
           }
         else
            beta=x[i];
         //--- solve alpha*x[i] = beta
         result=CBasicSolveAndUpdate(alpha,beta,lnmax,nrmb,maxgrowth,nrmx,vc);
         //--- check
         if(!result)
            return(result);
         x.Set(i,vc);
        }
      //--- return result
      return(result);
     }
//--- check
   if(IsUpper && trans==1)
     {
      //--- U^T*x = b
      for(i=0; i<n; i++)
        {
         //--- Task is reduced to alpha*x[i] = beta
         if(IsUnit)
            alpha=sa;
         else
            alpha=a.Get(i,i)*sa;
         beta=x[i];
         //--- solve alpha*x[i] = beta
         result=CBasicSolveAndUpdate(alpha,beta,lnmax,nrmb,maxgrowth,nrmx,vc);
         //--- check
         if(!result)
            return(result);
         x.Set(i,vc);
         //--- update the rest of right part
         if(i<n-1)
           {
            for(i_=i+1; i_<n; i_++)
               x.Set(i_,x[i_]-vc*sa*a.Get(i,i_));
           }
        }
      //--- return result
      return(result);
     }
//--- check
   if(!IsUpper && trans==1)
     {
      //--- L^T*x = b
      for(i=n-1; i>=0; i--)
        {
         //--- Task is reduced to alpha*x[i] = beta
         if(IsUnit)
            alpha=sa;
         else
            alpha=a.Get(i,i)*sa;
         beta=x[i];
         //--- solve alpha*x[i] = beta
         result=CBasicSolveAndUpdate(alpha,beta,lnmax,nrmb,maxgrowth,nrmx,vc);
         //--- check
         if(!result)
            return(result);
         x.Set(i,vc);
         //--- update the rest of right part
         if(i>0)
           {
            for(i_=0; i_<i; i_++)
               x.Set(i_,x[i_]-vc*sa*a.Get(i,i_));
           }
        }
      //--- return result
      return(result);
     }
//--- check
   if(IsUpper && trans==2)
     {
      //--- U^H*x=b
      for(i=0; i<n; i++)
        {
         //--- Task is reduced to alpha*x[i] = beta
         if(IsUnit)
            alpha=sa;
         else
            alpha=CMath::Conj(a.Get(i,i))*sa;
         beta=x[i];
         //--- solve alpha*x[i] = beta
         result=CBasicSolveAndUpdate(alpha,beta,lnmax,nrmb,maxgrowth,nrmx,vc);
         //--- check
         if(!result)
            return(result);
         x.Set(i,vc);
         //--- update the rest of right part
         if(i<n-1)
           {
            for(i_=i+1; i_<n; i_++)
               x.Set(i_,x[i_]-vc*sa*CMath::Conj(a.Get(i,i_)));
           }
        }
      //--- return result
      return(result);
     }
//--- check
   if(!IsUpper && trans==2)
     {
      //--- L^T*x = b
      for(i=n-1; i>=0; i--)
        {
         //--- Task is reduced to alpha*x[i] = beta
         if(IsUnit)
            alpha=sa;
         else
            alpha=CMath::Conj(a.Get(i,i))*sa;
         beta=x[i];
         //--- solve alpha*x[i] = beta
         result=CBasicSolveAndUpdate(alpha,beta,lnmax,nrmb,maxgrowth,nrmx,vc);
         //--- check
         if(!result)
            return(result);
         x.Set(i,vc);
         //--- update the rest of right part
         if(i>0)
           {
            for(i_=0; i_<i; i_++)
               x.Set(i_,x[i_]-vc*sa*CMath::Conj(a.Get(i,i_)));
           }
        }
      //--- return result
      return(result);
     }
//--- return result
   return(false);
  }
//+------------------------------------------------------------------+
//| complex basic solver-updater for reduced linear system           |
//|     alpha*x[i] = beta                                            |
//| solves this equation and updates it in overlfow-safe manner      |
//| (keeping track of relative growth of solution).                  |
//| Parameters:                                                      |
//|     Alpha   -   alpha                                            |
//|     Beta    -   beta                                             |
//|     LnMax   -   precomputed Ln(MaxRealNumber)                    |
//|     BNorm   -   inf-norm of b (right part of original system)    |
//|     MaxGrowth-  maximum growth of norm(x) relative to norm(b)    |
//|     XNorm   -   inf-norm of other components of X (which are     |
//|                 already processed) it is updated by              |
//|                 CBasicSolveAndUpdate.                            |
//|     X       -   solution                                         |
//+------------------------------------------------------------------+
bool CSafeSolve::CBasicSolveAndUpdate(complex &alpha,complex &beta,
                                      const double lnmax,const double bnorm,
                                      const double maxgrowth,
                                      double &xnorm,complex &x)
  {
   double v=0;
//--- initialization
   x=0;
//--- check
   if(alpha==0)
      return(false);
//--- check
   if(beta!=0)
     {
      //--- alpha*x[i]=beta
      v=MathLog(CMath::AbsComplex(beta))-MathLog(CMath::AbsComplex(alpha));
      //--- check
      if(v>lnmax)
         return(false);
      x=beta/alpha;
     }
   else
     {
      //--- alpha*x[i]=0
      x=0;
     }
//--- update NrmX, test growth limit
   xnorm=MathMax(xnorm,CMath::AbsComplex(x));
//--- check
   if(xnorm>maxgrowth*bnorm)
      return(false);
//--- return result
   return(true);
  }
//+------------------------------------------------------------------+
//| Dot-product                                                      |
//+------------------------------------------------------------------+
class CXblas
  {
public:
   static void       XDot(double &a[],double &b[],const int n,double &temp[],double &r,double &rerr);
   static void       XDot(CRowDouble &a,CRowDouble &b,const int n,CRowDouble &temp,double &r,double &rerr);
   static void       XCDot(complex &a[],complex &b[],const int n,double &temp[],complex &r,double &rerr);
   static void       XCDot(CRowComplex &a,CRowComplex &b,const int n,CRowDouble &temp,complex &r,double &rerr);

private:
   static void       XSum(CRowDouble &w,const double mx,const int n,double &r,double &rerr);
   static double     XFastPow(const double r,const int n);
  };
//+------------------------------------------------------------------+
//| More precise dot-product. Absolute error of subroutine result is |
//| about 1 ulp of max(MX,V), where:                                 |
//|     MX = max( |a[i]*b[i]| )                                      |
//|     V  = |(a,b)|                                                 |
//| INPUT PARAMETERS                                                 |
//|     A       -   array[0..N-1], vector 1                          |
//|     B       -   array[0..N-1], vector 2                          |
//|     N       -   vectors length, N<2^29.                          |
//|     Temp    -   array[0..N-1], pre-allocated temporary storage   |
//| OUTPUT PARAMETERS                                                |
//|     R       -   (A,B)                                            |
//|     RErr    -   estimate of error. This estimate accounts for    |
//|                 both errors during  calculation of (A,B) and     |
//|                errors introduced by rounding of A and B to fit in|
//| double (about 1 ulp).                                            |
//+------------------------------------------------------------------+
void CXblas::XDot(double &a[],double &b[],const int n,double &temp[],
                  double &r,double &rerr)
  {
   CRowDouble A=a;
   CRowDouble B=b;
   CRowDouble Temp=temp;
   XDot(A,B,n,Temp,r,rerr);
   Temp.ToArray(temp);
  }
//+------------------------------------------------------------------+
//| Same                                                             |
//+------------------------------------------------------------------+
void CXblas::XDot(CRowDouble &a,CRowDouble &b,const int n,CRowDouble &temp,
                  double &r,double &rerr)
  {
//--- create variables
   double mx=0;
   double v=0;
//--- initialization
   r=0;
   rerr=0;
//--- special cases:
//--- * N=0
   if(n==0)
     {
      r=0;
      rerr=0;
      //--- exit the function
      return;
     }
   mx=0;
//--- calculations
   for(int i=0; i<n; i++)
     {
      v=a[i]*b[i];
      temp.Set(i,v);
      mx=MathMax(mx,MathAbs(v));
     }
//--- check
   if(mx==0.0)
     {
      r=0;
      rerr=0;
      //--- exit the function
      return;
     }
//--- function call
   XSum(temp,mx,n,r,rerr);
  }
//+------------------------------------------------------------------+
//| More precise complex dot-product. Absolute error of subroutine   |
//| result is about 1 ulp of max(MX,V), where:                       |
//|     MX = max( |a[i]*b[i]| )                                      |
//|     V  = |(a,b)|                                                 |
//| INPUT PARAMETERS                                                 |
//|     A       -   array[0..N-1], vector 1                          |
//|     B       -   array[0..N-1], vector 2                          |
//|     N       -   vectors length, N<2^29.                          |
//|     Temp    -   array[0..2*N-1], pre-allocated temporary storage |
//| OUTPUT PARAMETERS                                                |
//|     R       -   (A,B)                                            |
//|     RErr    -   estimate of error. This estimate accounts for    |
//|                 both errors during  calculation of (A,B) and     |
//|                 errors introduced by rounding of A and B to fit  |
//|                 in double (about 1 ulp).                         |
//+------------------------------------------------------------------+
void CXblas::XCDot(complex &a[],complex &b[],const int n,double &temp[],
                   complex &r,double &rerr)
  {
   CRowComplex A=a;
   CRowComplex B=b;
   CRowDouble Temp=temp;
   XCDot(A,B,n,Temp,r,rerr);
   Temp.ToArray(temp);
  }
//+------------------------------------------------------------------+
//| Same                                                             |
//+------------------------------------------------------------------+
void CXblas::XCDot(CRowComplex &a,CRowComplex &b,const int n,CRowDouble &temp,
                   complex &r,double &rerr)
  {
//--- create variables
   int    i=0;
   double mx=0;
   double v=0;
   double rerrx=0;
   double rerry=0;
//--- initialization
   r=0;
   rerr=0;
//--- special cases:
//--- * N=0
   if(n==0)
     {
      r=0;
      rerr=0;
      //--- exit the function
      return;
     }
//--- calculate real part
   mx=0;
   for(i=0; i<n; i++)
     {
      //--- change values
      v=a[i].real*b[i].real;
      temp.Set(2*i,v);
      mx=MathMax(mx,MathAbs(v));
      v=-(a[i].imag*b[i].imag);
      temp.Set(2*i+1,v);
      mx=MathMax(mx,MathAbs(v));
     }
//--- check
   if(mx==0.0)
     {
      r.real=0;
      rerrx=0;
     }
   else
      XSum(temp,mx,2*n,r.real,rerrx);
//--- calculate imaginary part
   mx=0;
   for(i=0; i<n; i++)
     {
      //--- change values
      v=a[i].real*b[i].imag;
      temp.Set(2*i,v);
      mx=MathMax(mx,MathAbs(v));
      v=a[i].imag*b[i].real;
      temp.Set(2*i+1,v);
      mx=MathMax(mx,MathAbs(v));
     }
//--- check
   if(mx==0.0)
     {
      r.imag=0;
      rerry=0;
     }
   else
      XSum(temp,mx,2*n,r.imag,rerry);
//--- total error
   if(rerrx==0.0 && rerry==0.0)
      rerr=0;
   else
      rerr=MathMax(rerrx,rerry)*MathSqrt(1+CMath::Sqr(MathMin(rerrx,rerry)/MathMax(rerrx,rerry)));
  }
//+------------------------------------------------------------------+
//| Internal subroutine for extra-precise calculation of SUM(w[i]).  |
//| INPUT PARAMETERS:                                                |
//|     W   -   array[0..N-1], values to be added                    |
//|             W is modified during calculations.                   |
//|     MX  -   max(W[i])                                            |
//|     N   -   array size                                           |
//| OUTPUT PARAMETERS:                                               |
//|     R   -   SUM(w[i])                                            |
//|     RErr-   error estimate for R                                 |
//+------------------------------------------------------------------+
void CXblas::XSum(CRowDouble &w,const double mx,const int n,double &r,
                  double &rerr)
  {
//--- create variables
   int    i=0;
   int    k=0;
   int    ks=0;
   double v=0;
   double s=0;
   double ln2=0;
   double chunk=0;
   double invchunk=0;
   bool   allzeros;
   int    i_=0;
//--- initialization
   r=0;
   rerr=0;
//--- special cases:
//--- * N=0
//--- * N is too large to use integer arithmetics
   if(n==0)
     {
      r=0;
      rerr=0;
      //--- exit the function
      return;
     }
//--- check
   if(mx==0.0)
     {
      r=0;
      rerr=0;
      //--- exit the function
      return;
     }
//--- check
   if(!CAp::Assert(n<536870912,__FUNCTION__+": N is too large!"))
      return;
//--- Prepare
   ln2=MathLog(2);
   rerr=mx*CMath::m_machineepsilon;
//--- 1. find S such that 0.5<=S*MX<1
//--- 2. multiply W by S, so task is normalized in some sense
//--- 3. S:=1/S so we can obtain original vector multiplying by S
   k=(int)MathRound(MathLog(mx)/ln2);
   s=XFastPow(2,-k);
//--- check s
   if(!CMath::IsFinite(s))
     {
      //--- Overflow or underflow during evaluation of S; fallback low-precision code
      r=0;
      rerr=mx*CMath::m_machineepsilon;
      for(i=0; i<n; i++)
        {
         r=r+w[i];
        }
      return;
     }
//--- change s
   while(s*mx>=1.0)
      s=0.5*s;
   while(s*mx<0.5)
      s=2*s;
   for(i_=0; i_<n; i_++)
      w.Set(i_,s*w[i_]);
   s=1/s;
//--- find Chunk=2^M such that N*Chunk<2^29
//--- we have chosen upper limit (2^29) with enough space left
//--- to tolerate possible problems with rounding and N's close
//--- to the limit, so we don't want to be very strict here.
   k=(int)(MathLog((double)536870912/(double)n)/ln2);
   chunk=XFastPow(2,k);
//--- check
   if(chunk<2.0)
      chunk=2;
   invchunk=1/chunk;
//--- calculate result
   r=0;
   for(i_=0; i_<n; i_++)
      w.Set(i_,chunk*w[i_]);
//--- cycle
   while(true)
     {
      //--- change values
      s=s*invchunk;
      allzeros=true;
      ks=0;
      for(i=0; i<=n-1; i++)
        {
         v=w[i];
         k=(int)(v);
         //--- check
         if(v!=k)
            allzeros=false;
         w.Set(i,chunk*(v-k));
         ks=ks+k;
        }
      r=r+s*ks;
      v=MathAbs(r);
      //--- check
      if(allzeros || (s*n)==0.0)
         break;
     }
//--- correct error
   rerr=MathMax(rerr,MathAbs(r)*CMath::m_machineepsilon);
  }
//+------------------------------------------------------------------+
//| Fast Pow                                                         |
//+------------------------------------------------------------------+
double CXblas::XFastPow(const double r,const int n)
  {
   double result=0;
//--- check
   if(n>0)
     {
      //--- check
      if(n%2==0)
         result=CMath::Sqr(XFastPow(r,n/2));
      else
         result=r*XFastPow(r,n-1);
     }
   else
      //--- check
      if(n==0)
         result=1;
      else
         result=XFastPow(1/r,-n);
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| Auxiliary class for CLinMin                                      |
//+------------------------------------------------------------------+
struct CLinMinState
  {
   bool              m_brackt;
   bool              m_stage1;
   int               m_infoc;
   double            m_dg;
   double            m_dgm;
   double            m_dginit;
   double            m_dgtest;
   double            m_dgx;
   double            m_dgxm;
   double            m_dgy;
   double            m_dgym;
   double            m_finit;
   double            m_ftest1;
   double            m_fm;
   double            m_fx;
   double            m_fxm;
   double            m_fy;
   double            m_fym;
   double            m_stx;
   double            m_sty;
   double            m_stmin;
   double            m_stmax;
   double            m_width;
   double            m_width1;
   double            m_xtrapf;
   //--- constructor, destructor
                     CLinMinState(void) { ZeroMemory(this); }
                    ~CLinMinState(void) {}
   //--- create a copy
   void              Copy(const CLinMinState &obj);
   //--- overloading
   void              operator=(const CLinMinState &obj)  { Copy(obj); }
  };
//+------------------------------------------------------------------+
//| Create a copy                                                    |
//+------------------------------------------------------------------+
void CLinMinState::Copy(const CLinMinState &obj)
  {
//--- copy variables
   m_brackt=obj.m_brackt;
   m_stage1=obj.m_stage1;
   m_infoc=obj.m_infoc;
   m_dg=obj.m_dg;
   m_dgm=obj.m_dgm;
   m_dginit=obj.m_dginit;
   m_dgtest=obj.m_dgtest;
   m_dgx=obj.m_dgx;
   m_dgxm=obj.m_dgxm;
   m_dgy=obj.m_dgy;
   m_dgym=obj.m_dgym;
   m_finit=obj.m_finit;
   m_ftest1=obj.m_ftest1;
   m_fm=obj.m_fm;
   m_fx=obj.m_fx;
   m_fxm=obj.m_fxm;
   m_fy=obj.m_fy;
   m_fym=obj.m_fym;
   m_stx=obj.m_stx;
   m_sty=obj.m_sty;
   m_stmin=obj.m_stmin;
   m_stmax=obj.m_stmax;
   m_width=obj.m_width;
   m_width1=obj.m_width1;
   m_xtrapf=obj.m_xtrapf;
  }
//+------------------------------------------------------------------+
//| Auxiliary class for CLinMin                                      |
//+------------------------------------------------------------------+
struct CArmijoState
  {
   bool              m_needf;
   CRowDouble        m_x;
   double            m_f;
   int               m_n;
   CRowDouble        m_xbase;
   CRowDouble        m_s;
   double            m_stplen;
   double            m_fcur;
   double            m_stpmax;
   int               m_fmax;
   int               m_nfev;
   int               m_info;
   RCommState        m_rstate;
   //--- constructor, destructor
                     CArmijoState(void) { m_needf=0; m_f=0; m_n=0; m_stplen=0; m_fcur=0; m_stpmax=0; m_fmax=0; m_nfev=0; m_info=0; }
                    ~CArmijoState(void) {}
   //--- create a copy
   void              Copy(CArmijoState &obj);
   //--- overloading
   void              operator=(CArmijoState &obj) { Copy(obj); }
  };
//+------------------------------------------------------------------+
//| Copy                                                             |
//+------------------------------------------------------------------+
void CArmijoState::Copy(CArmijoState &obj)
  {
   m_needf=obj.m_needf;
   m_x=obj.m_x;
   m_f=obj.m_f;
   m_n=obj.m_n;
   m_xbase=obj.m_xbase;
   m_s=obj.m_s;
   m_stplen=obj.m_stplen;
   m_fcur=obj.m_fcur;
   m_stpmax=obj.m_stpmax;
   m_fmax=obj.m_fmax;
   m_nfev=obj.m_nfev;
   m_info=obj.m_info;
   m_rstate=obj.m_rstate;
  }
//+------------------------------------------------------------------+
//| Minimization of linear forms                                     |
//+------------------------------------------------------------------+
class CLinMin
  {
public:
   //--- class constants
   static const double m_ftol;
   static const double m_xtol;
   static const int  m_maxfev;
   static const double m_stpmin;
   static const double m_defstpmax;
   static const double m_armijofactor;
   //--- public methods
   static void       LinMinNormalized(double &d[],double &stp,const int n);
   static void       LinMinNormalized(CRowDouble &d,double &stp,const int n);
   static void       MCSrch(const int n,double &x[],double &f,double &g[],double &s[],double &stp,double stpmax,double gtol,int &info,int &nfev,double &wa[],CLinMinState &state,int &stage);
   static void       MCSrch(const int n,CRowDouble &x,double &f,CRowDouble &g,CRowDouble &s,double &stp,double stpmax,double gtol,int &info,int &nfev,CRowDouble &wa,CLinMinState &state,int &stage);
   static void       ArmijoCreate(const int n,double &x[],const double f,double &s[],const double stp,const double stpmax,const int ffmax,CArmijoState &state);
   static void       ArmijoCreate(const int n,CRowDouble &x,const double f,CRowDouble &s,const double stp,const double stpmax,const int ffmax,CArmijoState &state);
   static void       ArmijoResults(CArmijoState &state,int &info,double &stp,double &f);
   static bool       ArmijoIteration(CArmijoState &state);

private:
   //--- private methods
   static void       MCStep(double &stx,double &fx,double &dx,double &sty,double &fy,double &dy,double &stp,double fp,double dp,bool &m_brackt,double stmin,double stmax,int &info);
   //--- auxiliary functions for ArmijoIteration
   static void       Func_lbl_rcomm(CArmijoState &state,int n,double v);
   static bool       Func_lbl_6(CArmijoState &state,int &n,double &v);
   static bool       Func_lbl_10(CArmijoState &state,int &n,double &v);
  };
//+------------------------------------------------------------------+
//| Initialize constants                                             |
//+------------------------------------------------------------------+
const double CLinMin::m_ftol=0.001;
const double CLinMin::m_xtol=100*CMath::m_machineepsilon;
const int    CLinMin::m_maxfev=20;
const double CLinMin::m_stpmin=1.0E-50;
const double CLinMin::m_defstpmax=1.0E+50;
const double CLinMin::m_armijofactor=1.3;
//+------------------------------------------------------------------+
//| Normalizes direction/step pair: makes |D|=1,scales Stp.          |
//| If |D|=0,it returns,leavind D/Stp unchanged.                     |
//+------------------------------------------------------------------+
void CLinMin::LinMinNormalized(double &d[],double &stp,const int n)
  {
   CRowDouble D=d;
   LinMinNormalized(D,stp,n);
   D.ToArray(d);
  }
//+------------------------------------------------------------------+
//| Normalizes direction/step pair: makes |D|=1,scales Stp.          |
//| If |D|=0,it returns,leavind D/Stp unchanged.                     |
//+------------------------------------------------------------------+
void CLinMin::LinMinNormalized(CRowDouble &d,double &stp,const int n)
  {
//--- create variables
   double mx=0;
   double s=0;
   int    i=0;
   int    i_=0;
//--- first, scale D to avoid underflow/overflow durng squaring
   mx=0;
   for(i=0; i<n; i++)
      mx=MathMax(mx,MathAbs(d[i]));
//--- check
   if(mx==0.0)
      return;
   s=1/mx;
   for(i_=0; i_<n; i_++)
      d.Set(i_,s*d[i_]);
   stp=stp/s;
//--- normalize D
   s=0.0;
   for(i_=0; i_<n; i_++)
      s+=d[i_]*d[i_];
   s=1/MathSqrt(s);
   for(i_=0; i_<=n-1; i_++)
      d.Set(i_,s*d[i_]);
   stp=stp/s;
  }
//+------------------------------------------------------------------+
//| The purpose of MCSrch is to find a step which satisfies a        |
//| sufficient decrease condition and a curvature condition.         |
//| At each stage the subroutine updates an interval of uncertainty  |
//| with endpoints stx and sty. The interval of uncertainty is       |
//| initially chosen so that it contains a minimizer of the modified |
//| function                                                         |
//|     f(x+stp*s) - f(x) - ftol*stp*(gradf(x)'s).                   |
//| If a step is obtained for which the modified function has a      |
//| nonpositive function value and nonnegative derivative, then the  |
//| interval of uncertainty is chosen so that it contains a minimizer|
//| of f(x+stp*s).                                                   |
//| The  algorithm  is  designed to find a step which satisfies the  |
//| sufficient decrease condition                                    |
//|     f(x+stp*s) .le. f(x) + ftol*stp*(gradf(x)'s),                |
//| and the curvature condition                                      |
//|     abs(gradf(x+stp*s)'s)) .le. gtol*abs(gradf(x)'s).            |
//| If  ftol is less than gtol and if, for example, the function is  |
//| bounded below, then there is always a step which satisfies both  |
//| conditions. If no step can be found which satisfies both         |
//| conditions, then the algorithm usually stops when rounding errors|
//| prevent further progress. In this case stp only satisfies the    |
//| sufficient decrease condition.                                   |
//| :::::::::::::important notes:::::::::::::                        |
//| note 1:                                                          |
//| This routine  guarantees that it will stop at the last point     |
//| where function value was calculated. It won't make several       |
//| additional function evaluations after finding good point. So if  |
//| you store function evaluations requested by this routine, you can|
//| be sure that last one is the point where we've stopped.          |
//| NOTE 2:                                                          |
//| when 0<StpMax<StpMin, algorithm will terminate with INFO=5 and   |
//| Stp=0.0                                                          |
//| :::::::::::::::::::::::::::::::::::::::::                        |
//| Parameters descriprion                                           |
//| Stage is zero on first call, zero on final exit                  |
//| N is a positive integer input variable set to the number of      |
//| variables.                                                       |
//| X is  an  array  of  length n. on input it must contain the base |
//| point for the line search. on output it contains x+stp*s.        |
//| F is  a  variable. on input it must contain the value of f at x. |
//| on output it contains the value of f at x + stp*s.               |
//| G is an array of length n. on input it must contain the gradient |
//| of f at x. on output it contains the gradient of f at x + stp*s. |
//| S is an input array of length n which specifies the search       |
//| direction.                                                       |
//| Stp  is  a nonnegative variable. on input stp contains an initial|
//| estimate of a satisfactory step. on output stp contains the final|
//| estimate.                                                        |
//| Ftol and gtol are nonnegative input variables. termination occurs|
//| when the sufficient decrease condition and the directional       |
//| derivative condition are satisfied.                              |
//| Xtol is a nonnegative input variable. termination occurs when the|
//| relative width of the interval of uncertainty is at most xtol.   |
//| Stpmin and stpmax are nonnegative input variables which specify  |
//| lower and upper bounds for the step.                             |
//| Maxfev is a positive integer input variable. termination occurs  |
//| when the number of calls to fcn is at least maxfev by the end of |
//| an iteration.                                                    |
//| Info is an integer output variable set as follows:               |
//|     info = 0  improper input parameters.                         |
//|     info = 1  the sufficient decrease condition and the          |
//|               directional derivative condition hold.             |
//|     info = 2  relative width of the interval of uncertainty      |
//|              is at most xtol.                                    |
//|     info = 3  number of calls to fcn has reached maxfev.         |
//|     info = 4  the step is at the lower bound stpmin.             |
//|     info = 5  the step is at the upper bound stpmax.             |
//|     info = 6  rounding errors prevent further progress.          |
//|               there may not be a step which satisfies the        |
//|               sufficient decrease and curvature conditions.      |
//|               tolerances may be too small.                       |
//| Nfev is an integer output variable set to the number of calls to |
//| fcn.                                                             |
//| wa is a work array of length n.                                  |
//| argonne national laboratory. minpack project. june 1983          |
//| Jorge J. More', David J. Thuente                                 |
//+------------------------------------------------------------------+
void CLinMin::MCSrch(const int n,double &x[],double &f,double &g[],
                     double &s[],double &stp,double stpmax,double gtol,
                     int &info,int &nfev,double &wa[],
                     CLinMinState &state,int &stage)
  {
   CRowDouble X=x;
   CRowDouble G=g;
   CRowDouble S=s;
   CRowDouble WA=wa;
   MCSrch(n,X,f,G,S,stp,stpmax,gtol,info,nfev,WA,state,stage);
   X.ToArray(x);
   G.ToArray(g);
   WA.ToArray(wa);
  }
//+------------------------------------------------------------------+
//| Same                                                             |
//+------------------------------------------------------------------+
void CLinMin::MCSrch(const int n,CRowDouble &x,double &f,CRowDouble &g,
                     CRowDouble &s,double &stp,double stpmax,double gtol,
                     int &info,int &nfev,CRowDouble &wa,
                     CLinMinState &state,int &stage)
  {
//--- create variables
   double v=0;
   double p5=0.5;
   double p66=0.66;
   double zero=0;
   int    i_=0;
//--- init
   state.m_xtrapf=4.0;
//--- check
   if(stpmax==0.0)
      stpmax=m_defstpmax;
//--- check
   if(stp<m_stpmin)
      stp=m_stpmin;
//--- check
   if(stp>stpmax)
      stp=stpmax;
//--- Main cycle
   while(true)
     {
      switch(stage)
        {
         case 0:
            //--- NEXT
            stage=2;
            continue;
         case 2:
            state.m_infoc=1;
            info=0;
            //--- check the input parameters for errors.
            if(stpmax<m_stpmin && stpmax>0.0)
              {
               info=5;
               stp=stpmax;
               stage=0;
               //--- exit the function
               return;
              }
            //--- check
            if(n<=0 || stp<=0.0 || m_ftol<0.0 || gtol<zero || m_xtol<zero || m_stpmin<zero || stpmax<m_stpmin || m_maxfev<=0)
              {
               stage=0;
               return;
              }
            //--- compute the initial gradient in the search direction
            //--- and check that s is a descent direction.
            v=CAblasF::RDotV(n,g,s);
            state.m_dginit=v;
            //--- check
            if(state.m_dginit>=0.0)
              {
               stage=0;
               return;
              }
            //--- initialize local variables.
            state.m_brackt=false;
            state.m_stage1=true;
            nfev=0;
            state.m_finit=f;
            state.m_dgtest=m_ftol*state.m_dginit;
            state.m_width=stpmax-m_stpmin;
            state.m_width1=state.m_width/p5;
            wa=x;
            //--- the variables stx,fx,dgx contain the values of the step,
            //--- function,and directional derivative at the best step.
            //--- the variables sty,fy,dgy contain the value of the step,
            //--- function,and derivative at the other endpoint of
            //--- the interval of uncertainty.
            //--- the variables stp,f,dg contain the values of the step,
            //--- function,and derivative at the current step.
            state.m_stx=0;
            state.m_fx=state.m_finit;
            state.m_dgx=state.m_dginit;
            state.m_sty=0;
            state.m_fy=state.m_finit;
            state.m_dgy=state.m_dginit;
            //--- next
            stage=3;
            continue;
         case 3:
            //--- start of iteration.
            //--- set the minimum and maximum steps to correspond
            //--- to the present interval of uncertainty.
            if(state.m_brackt)
              {
               //--- check
               if(state.m_stx<state.m_sty)
                 {
                  state.m_stmin=state.m_stx;
                  state.m_stmax=state.m_sty;
                 }
               else
                 {
                  state.m_stmin=state.m_sty;
                  state.m_stmax=state.m_stx;
                 }
              }
            else
              {
               state.m_stmin=state.m_stx;
               state.m_stmax=stp+state.m_xtrapf*(stp-state.m_stx);
              }
            //--- force the step to be within the bounds stpmax and stpmin.
            if(stp>stpmax)
               stp=stpmax;
            //--- check
            if(stp<m_stpmin)
               stp=m_stpmin;
            //--- if an unusual termination is to occur then let
            //--- stp be the lowest point obtained so far.
            if((state.m_brackt && (stp<=state.m_stmin || stp>=state.m_stmax)) || nfev>=m_maxfev-1 || state.m_infoc==0 ||
               (state.m_brackt && state.m_stmax-state.m_stmin<=m_xtol*state.m_stmax))
               stp=state.m_stx;
            //--- evaluate the function and gradient at stp
            //--- and compute the directional derivative.
            for(i_=0; i_<n; i_++)
               x.Set(i_,wa[i_]+s[i_]*stp);
            //--- NEXT
            stage=4;
            return;
         case 4:
            info=0;
            nfev++;
            v=CAblasF::RDotV(n,g,s);
            state.m_dg=v;
            state.m_ftest1=state.m_finit+stp*state.m_dgtest;
            //--- test for convergence.
            if((state.m_brackt && (stp<=state.m_stmin || stp>=state.m_stmax)) || state.m_infoc==0)
               info=6;
            //--- check
            if((stp==stpmax && f<=state.m_ftest1) && state.m_dg<=state.m_dgtest)
               info=5;
            //--- check
            if(stp==m_stpmin && (f>state.m_ftest1 || state.m_dg>=state.m_dgtest))
               info=4;
            //--- check
            if(nfev>=m_maxfev)
               info=3;
            //--- check
            if(state.m_brackt && state.m_stmax-state.m_stmin<=m_xtol*state.m_stmax)
               info=2;
            //--- check
            if(f<=state.m_ftest1 && MathAbs(state.m_dg)<=-(gtol*state.m_dginit))
               info=1;
            //--- check for termination.
            if(info!=0)
              {
               //--- Check guarantees provided by the function for INFO=1 or INFO=5
               if(info==1 || info==5)
                 {
                  v=MathPow(wa.ToVector()-x.ToVector(),2.0).Sum();
                  if(f>=state.m_finit || v==0.0)
                     info=6;
                 }
               stage=0;
               return;
              }
            //--- in the first stage we seek a step for which the modified
            //--- function has a nonpositive value and nonnegative derivative.
            if((state.m_stage1 && f<=state.m_ftest1) && state.m_dg>=MathMin(m_ftol,gtol)*state.m_dginit)
               state.m_stage1=false;
            //--- a modified function is used to predict the step only if
            //--- we have not obtained a step for which the modified
            //--- function has a nonpositive function value and nonnegative
            //--- derivative,and if a lower function value has been
            //--- obtained but the decrease is not sufficient.
            if((state.m_stage1 && f<=state.m_fx) && f>state.m_ftest1)
              {
               //--- define the modified function and derivative values.
               state.m_fm=f-stp*state.m_dgtest;
               state.m_fxm=state.m_fx-state.m_stx*state.m_dgtest;
               state.m_fym=state.m_fy-state.m_sty*state.m_dgtest;
               state.m_dgm=state.m_dg-state.m_dgtest;
               state.m_dgxm=state.m_dgx-state.m_dgtest;
               state.m_dgym=state.m_dgy-state.m_dgtest;
               //--- call cstep to update the interval of uncertainty
               //--- and to compute the new step.
               MCStep(state.m_stx,state.m_fxm,state.m_dgxm,state.m_sty,state.m_fym,state.m_dgym,stp,state.m_fm,state.m_dgm,state.m_brackt,state.m_stmin,state.m_stmax,state.m_infoc);
               //--- reset the function and gradient values for f.
               state.m_fx=state.m_fxm+state.m_stx*state.m_dgtest;
               state.m_fy=state.m_fym+state.m_sty*state.m_dgtest;
               state.m_dgx=state.m_dgxm+state.m_dgtest;
               state.m_dgy=state.m_dgym+state.m_dgtest;
              }
            else
              {
               //--- call mcstep to update the interval of uncertainty
               //--- and to compute the new step.
               MCStep(state.m_stx,state.m_fx,state.m_dgx,state.m_sty,state.m_fy,state.m_dgy,stp,f,state.m_dg,state.m_brackt,state.m_stmin,state.m_stmax,state.m_infoc);
              }
            //--- force a sufficient decrease in the size of the
            //--- interval of uncertainty.
            if(state.m_brackt)
              {
               //--- check
               if(MathAbs(state.m_sty-state.m_stx)>=p66*state.m_width1)
                  stp=state.m_stx+p5*(state.m_sty-state.m_stx);
               state.m_width1=state.m_width;
               state.m_width=MathAbs(state.m_sty-state.m_stx);
              }
            //--- next.
            stage=3;
            continue;
        }
     }
  }
//+------------------------------------------------------------------+
//| These functions perform Armijo line search using at most FMAX    |
//| function evaluations. It doesn't enforce some kind of            |
//| "sufficient decrease" criterion - it just tries different Armijo |
//| steps and returns optimum found so far.                          |
//| Optimization is done using F-rcomm interface:                    |
//| * ArmijoCreate initializes State structure                       |
//|   (reusing previously allocated buffers)                         |
//| * ArmijoIteration is subsequently called                         |
//| * ArmijoResults returns results                                  |
//| INPUT PARAMETERS:                                                |
//|     N       -   problem size                                     |
//|     X       -   array[N], starting point                         |
//|     F       -   F(X+S*STP)                                       |
//|     S       -   step direction, S>0                              |
//|     STP     -   step length                                      |
//|     STPMAX  -   maximum value for STP or zero (if no limit is    |
//|                 imposed)                                         |
//|     FMAX    -   maximum number of function evaluations           |
//|     State   -   optimization state                               |
//+------------------------------------------------------------------+
void CLinMin::ArmijoCreate(const int n,double &x[],const double f,
                           double &s[],const double stp,const double stpmax,
                           const int ffmax,CArmijoState &state)
  {
   CRowDouble X=x;
   CRowDouble S=s;
   ArmijoCreate(n,X,f,S,stp,stpmax,ffmax,state);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CLinMin::ArmijoCreate(const int n,CRowDouble &x,const double f,
                           CRowDouble &s,const double stp,const double stpmax,
                           const int ffmax,CArmijoState &state)
  {
//--- check
   if(CAp::Len(state.m_x)<n)
      state.m_x.Resize(n);
//--- copy
   state.m_stpmax=stpmax;
   state.m_fmax=ffmax;
   state.m_stplen=stp;
   state.m_fcur=f;
   state.m_n=n;
   state.m_xbase=x;
//--- check
   if(CAp::Len(state.m_xbase)!=n)
      state.m_xbase.Resize(n);
   state.m_s=s;
//--- check
   if(CAp::Len(state.m_s)!=n)
      state.m_s.Resize(n);
//--- allocation
   state.m_rstate.ia.Resize(1);
   state.m_rstate.ra.Resize(1);
   state.m_rstate.stage=-1;
  }
//+------------------------------------------------------------------+
//| Results of Armijo search                                         |
//| OUTPUT PARAMETERS:                                               |
//|     INFO    -   on output it is set to one of the return codes:  |
//|                 * 0     improper input params                    |
//|                 * 1     optimum step is found with at most FMAX  |
//|                         evaluations                              |
//|                 * 3     FMAX evaluations were used,              |
//|                         X contains optimum found so far          |
//|                 * 4     step is at lower bound STPMIN            |
//|                 * 5     step is at upper bound                   |
//|     STP     -   step length (in case of failure it is still      |
//|                 returned)                                        |
//|     F       -   function value (in case of failure it is still   |
//|                 returned)                                        |
//+------------------------------------------------------------------+
void CLinMin::ArmijoResults(CArmijoState &state,int &info,
                            double &stp,double &f)
  {
//--- change values
   info=state.m_info;
   stp=state.m_stplen;
   f=state.m_fcur;
  }
//+------------------------------------------------------------------+
//| Internal subroutine for MCSrch                                   |
//+------------------------------------------------------------------+
void CLinMin::MCStep(double &stx,double &fx,double &dx,double &sty,
                     double &fy,double &dy,double &stp,double fp,
                     double dp,bool &m_brackt,double stmin,
                     double stmax,int &info)
  {
//--- create variables
   bool   bound;
   double gamma=0;
   double p=0;
   double q=0;
   double r=0;
   double s=0;
   double sgnd=0;
   double stpc=0;
   double stpf=0;
   double stpq=0;
   double theta=0;
//--- initialization
   info=0;
//--- check the input parameters for errors.
   if(((m_brackt && (stp<=MathMin(stx,sty) || stp>=MathMax(stx,sty))) || dx*(stp-stx)>=0.0) || stmax<stmin)
      return;
//--- determine if the derivatives have opposite sign.
   sgnd=dp*(dx/MathAbs(dx));
//--- first case. a higher function value.
//--- the minimum is bracketed. if the cubic step is closer
//--- to stx than the quadratic step,the cubic step is taken,
//--- else the average of the cubic and quadratic steps is taken.
   if(fp>fx)
     {
      //--- initialization
      info=1;
      bound=true;
      theta=3*(fx-fp)/(stp-stx)+dx+dp;
      s=MathMax(MathAbs(theta),MathMax(MathAbs(dx),MathAbs(dp)));
      gamma=(s==0?0:s*MathSqrt(CMath::Sqr(theta/s)-dx/s*(dp/s)));
      //--- check
      if(stp<stx)
         gamma=-gamma;
      //--- initialization
      p=gamma-dx+theta;
      q=gamma-dx+gamma+dp;
      r=p/q;
      stpc=stx+r*(stp-stx);
      stpq=stx+dx/((fx-fp)/(stp-stx)+dx)/2*(stp-stx);
      //--- check
      if(MathAbs(stpc-stx)<MathAbs(stpq-stx))
         stpf=stpc;
      else
         stpf=stpc+(stpq-stpc)/2;
      m_brackt=true;
     }
   else
     {
      //--- check
      if(sgnd<0.0)
        {
         //--- second case. a lower function value and derivatives of
         //--- opposite sign. the minimum is bracketed. if the cubic
         //--- step is closer to stx than the quadratic (secant) step,
         //--- the cubic step is taken, else the quadratic step is taken.
         info=2;
         bound=false;
         theta=3*(fx-fp)/(stp-stx)+dx+dp;
         s=MathMax(MathAbs(theta),MathMax(MathAbs(dx),MathAbs(dp)));
         gamma=s*MathSqrt(CMath::Sqr(theta/s)-dx/s*(dp/s));
         //--- check
         if(stp>stx)
            gamma=-gamma;
         //--- initialization
         p=gamma-dp+theta;
         q=gamma-dp+gamma+dx;
         r=p/q;
         stpc=stp+r*(stx-stp);
         stpq=stp+dp/(dp-dx)*(stx-stp);
         //--- check
         if(MathAbs(stpc-stp)>MathAbs(stpq-stp))
            stpf=stpc;
         else
            stpf=stpq;
         m_brackt=true;
        }
      else
        {
         //--- check
         if(MathAbs(dp)<MathAbs(dx))
           {
            //--- third case. a lower function value,derivatives of the
            //--- same sign, and the magnitude of the derivative decreases.
            //--- the cubic step is only used if the cubic tends to infinity
            //--- in the direction of the step or if the minimum of the cubic
            //--- is beyond stp. otherwise the cubic step is defined to be
            //--- either stpmin or stpmax. the quadratic (secant) step is also
            //--- computed and if the minimum is bracketed then the the step
            //--- closest to stx is taken, else the step farthest away is taken.
            info=3;
            bound=true;
            theta=3*(fx-fp)/(stp-stx)+dx+dp;
            s=MathMax(MathAbs(theta),MathMax(MathAbs(dx),MathAbs(dp)));
            //--- the case gamma=0 only arises if the cubic does not tend
            //--- to infinity in the direction of the step.
            gamma=s*MathSqrt(MathMax(0,CMath::Sqr(theta/s)-dx/s*(dp/s)));
            //--- check
            if(stp>stx)
               gamma=-gamma;
            p=gamma-dp+theta;
            q=gamma+(dx-dp)+gamma;
            r=p/q;
            //--- check
            if(r<0.0 && (double)(gamma)!=0.0)
               stpc=stp+r*(stx-stp);
            else
              {
               //--- check
               if(stp>stx)
                  stpc=stmax;
               else
                  stpc=stmin;
              }
            stpq=stp+dp/(dp-dx)*(stx-stp);
            //--- check
            if(m_brackt)
              {
               //--- check
               if(MathAbs(stp-stpc)<MathAbs(stp-stpq))
                  stpf=stpc;
               else
                  stpf=stpq;
              }
            else
              {
               //--- check
               if(MathAbs(stp-stpc)>MathAbs(stp-stpq))
                  stpf=stpc;
               else
                  stpf=stpq;
              }
           }
         else
           {
            //--- fourth case. a lower function value,derivatives of the
            //--- same sign, and the magnitude of the derivative does
            //--- not decrease. if the minimum is not bracketed, the step
            //--- is either stpmin or stpmax, else the cubic step is taken.
            info=4;
            bound=false;
            //--- check
            if(m_brackt)
              {
               theta=3*(fp-fy)/(sty-stp)+dy+dp;
               s=MathMax(MathAbs(theta),MathMax(MathAbs(dy),MathAbs(dp)));
               gamma=s*MathSqrt(CMath::Sqr(theta/s)-dy/s*(dp/s));
               //--- check
               if(stp>sty)
                  gamma=-gamma;
               //--- initialization
               p=gamma-dp+theta;
               q=gamma-dp+gamma+dy;
               r=p/q;
               stpc=stp+r*(sty-stp);
               stpf=stpc;
              }
            else
              {
               //--- check
               if(stp>stx)
                  stpf=stmax;
               else
                  stpf=stmin;
              }
           }
        }
     }
//--- update the interval of uncertainty. this update does not
//--- depend on the new step or the case analysis above.
   if(fp>fx)
     {
      //--- set value
      sty=stp;
      fy=fp;
      dy=dp;
     }
   else
     {
      //--- check
      if(sgnd<0.0)
        {
         //--- set value
         sty=stx;
         fy=fx;
         dy=dx;
        }
      //--- set value
      stx=stp;
      fx=fp;
      dx=dp;
     }
//--- compute the new step and safeguard it.
   stpf=MathMin(stmax,stpf);
   stpf=MathMax(stmin,stpf);
   stp=stpf;
//--- check
   if(m_brackt && bound)
     {
      //--- check
      if(sty>stx)
         stp=MathMin(stx+0.66*(sty-stx),stp);
      else
         stp=MathMax(stx+0.66*(sty-stx),stp);
     }
  }
//+------------------------------------------------------------------+
//| This is rcomm-based search function                              |
//+------------------------------------------------------------------+
bool CLinMin::ArmijoIteration(CArmijoState &state)
  {
//--- create variables
   double v=0;
   int    n=0;
   int    i_=0;
//--- This code initializes locals by:
//--- * random values determined during code
//---   generation - on first subroutine call
//--- * values from previous call - on subsequent calls
   if(state.m_rstate.stage>=0)
     {
      //--- initialization
      n=state.m_rstate.ia[0];
      v=state.m_rstate.ra[0];
     }
   else
     {
      //--- initialization
      n=-983;
      v=-989;
     }
//--- check
   if(state.m_rstate.stage==0)
     {
      state.m_nfev=state.m_nfev+1;
      //--- check
      if(state.m_f>=state.m_fcur)
        {
         //--- Decrease length
         v=state.m_stplen/m_armijofactor;
         //--- copy
         state.m_x=state.m_xbase.ToVector()+state.m_s*v;
         state.m_rstate.stage=2;
         //--- Saving state
         Func_lbl_rcomm(state,n,v);
         //--- return result
         return(true);
        }
      //--- change values
      state.m_stplen=v;
      state.m_fcur=state.m_f;
      //--- function call, return result
      return(Func_lbl_6(state,n,v));
     }
//--- check
   if(state.m_rstate.stage==1)
     {
      state.m_nfev++;
      //--- make decision
      if(state.m_f<state.m_fcur)
        {
         //--- change values
         state.m_stplen=v;
         state.m_fcur=state.m_f;
        }
      else
        {
         state.m_info=1;
         //--- return result
         return(false);
        }
      //--- function call, return result
      return(Func_lbl_6(state,n,v));
     }
//--- check
   if(state.m_rstate.stage==2)
     {
      state.m_nfev++;
      //--- check
      if(state.m_f>=state.m_fcur)
        {
         //--- Nothing to be done
         state.m_info=1;
         //--- return result
         return(false);
        }
      //--- change values
      state.m_stplen/=m_armijofactor;
      state.m_fcur=state.m_f;
      //--- function call, return result
      return(Func_lbl_10(state,n,v));
     }
//--- check
   if(state.m_rstate.stage==3)
     {
      state.m_nfev++;
      //--- make decision
      if(state.m_f<state.m_fcur)
        {
         //--- change values
         state.m_stplen/=m_armijofactor;
         state.m_fcur=state.m_f;
        }
      else
        {
         state.m_info=1;
         //--- return result
         return(false);
        }
      return(Func_lbl_10(state,n,v));
     }
//--- Routine body
   if((state.m_stplen<=0.0 || state.m_stpmax<0.0) || state.m_fmax<2)
     {
      state.m_info=0;
      //--- return result
      return(false);
     }
//--- check
   if(state.m_stplen<=m_stpmin)
     {
      state.m_info=4;
      //--- return result
      return(false);
     }
//--- change values
   n=state.m_n;
   state.m_nfev=0;
//--- We always need F
   state.m_needf=true;
//--- Bound StpLen
   if(state.m_stplen>state.m_stpmax && state.m_stpmax!=0.0)
      state.m_stplen=state.m_stpmax;
//--- Increase length
   v=state.m_stplen*m_armijofactor;
//--- check
   if(v>state.m_stpmax && state.m_stpmax!=0.0)
      v=state.m_stpmax;
//--- copy
   state.m_x=state.m_xbase.ToVector()+state.m_s*v;
   state.m_rstate.stage=0;
//--- Saving state
   Func_lbl_rcomm(state,n,v);
//--- return result
   return(true);
  }
//+------------------------------------------------------------------+
//| Auxiliary function for ArmijoIteration. Is a product to get rid  |
//| of the operator unconditional jump goto.                         |
//+------------------------------------------------------------------+
void CLinMin::Func_lbl_rcomm(CArmijoState &state,int n,double v)
  {
//--- save
   state.m_rstate.ia.Set(0,n);
   state.m_rstate.ra.Set(0,v);
  }
//+------------------------------------------------------------------+
//| Auxiliary function for ArmijoIteration. Is a product to get rid  |
//| of the operator unconditional jump goto.                         |
//+------------------------------------------------------------------+
bool CLinMin::Func_lbl_6(CArmijoState &state,int &n,double &v)
  {
//--- test stopping conditions
   if(state.m_nfev>=state.m_fmax)
     {
      state.m_info=3;
      //--- return result
      return(false);
     }
//--- check
   if(state.m_stplen>=state.m_stpmax)
     {
      state.m_info=5;
      //--- return result
      return(false);
     }
//--- evaluate F
   v=state.m_stplen*m_armijofactor;
//--- check
   if(v>state.m_stpmax && state.m_stpmax!=0.0)
      v=state.m_stpmax;
//--- copy
   state.m_x=state.m_xbase.ToVector()+state.m_s*v;
   state.m_rstate.stage=1;
//--- Saving state
   Func_lbl_rcomm(state,n,v);
//--- return result
   return(true);
  }
//+------------------------------------------------------------------+
//| Auxiliary function for ArmijoIteration. Is a product to get rid  |
//| of the operator unconditional jump goto.                         |
//+------------------------------------------------------------------+
bool CLinMin::Func_lbl_10(CArmijoState &state,int &n,double &v)
  {
//--- test stopping conditions
   if(state.m_nfev>=state.m_fmax)
     {
      state.m_info=3;
      //--- return result
      return(false);
     }
//--- check
   if(state.m_stplen<=m_stpmin)
     {
      state.m_info=4;
      //--- return result
      return(false);
     }
//--- evaluate F
   v=state.m_stplen/m_armijofactor;
//--- copy
   state.m_x=state.m_xbase.ToVector()+state.m_s*v;
   state.m_rstate.stage=3;
//--- Saving state
   Func_lbl_rcomm(state,n,v);
//--- return result
   return(true);
  }
//+------------------------------------------------------------------+
//| This record stores execution plan for the fast transformation    |
//| along with preallocated temporary buffers and precalculated      |
//| values.                                                          |
//| FIELDS:                                                          |
//|   Entries     -  plan entries, one row = one entry (see below for|
//|                  description).                                   |
//|   Buf0,Buf1,Buf2 - global temporary buffers; some of them are    |
//|                  allocated, some of them are not (as decided by  |
//|                  plan generation subroutine).                    |
//|   Buffer      -  global buffer whose size is equal to plan size. |
//|                  There is one-to-one correspondence between      |
//|                  elements of global buffer and elements of array |
//|                  transformed. Because of it global buffer can be |
//|                  used as temporary thread-safe storage WITHOUT   |
//|                  ACQUIRING LOCK - each worker thread works with  |
//|                  its part of input array, and each part of input |
//|                  array corresponds to distinct part of buffer.   |
//| FORMAT OF THE ENTRIES TABLE:                                     |
//| Entries table is 2D array which stores one entry per row. Row    |
//| format is:                                                       |
//|   row[0]         operation type:                                 |
//|           * 0 for "end of plan/subplan"                        |
//|           *+1 for "reference O(N^2) complex FFT"               |
//|            * -1 for complex transposition                        |
//|            * -2 for multiplication by twiddle factors of complex |
//|                 FFT                                              |
//|           *-3 for "start of plan/subplan"                      |
//|   row[1]         repetition count, >=1                           |
//|   row[2]         base operand size (number of microvectors), >=1 |
//|   row[3]         microvector size (measured in real numbers), >=1|
//|   row[4]         parameter0, meaning depends on row[0]           |
//|   row[5]         parameter1, meaning depends on row[0]           |
//| FORMAT OF THE DATA:                                              |
//| Transformation plan works with row[1]*row[2]*row[3] real numbers,|
//| which are (in most cases) interpreted as sequence of complex     |
//| numbers. These data are grouped as follows:                      |
//|   * we have row[1] contiguous OPERANDS, which can be treated     |
//|     separately                                                   |
//|   * each operand includes row[2] contiguous MICROVECTORS         |
//|   * each microvector includes row[3] COMPONENTS, which can be    |
//|     treated separately                                           |
//|   * pair of components form complex number, so in most cases     |
//|     row[3] will be even                                          |
//| Say, if you want to perform complex FFT of length 3, then:       |
//|   * you have 1 operand: row[1]=1                                 |
//|   * operand consists of 3 microvectors:   row[2]=3               |
//|   * each microvector has two components:  row[3]=2               |
//|   * a pair of subsequent components is treated as complex number |
//|     if you want to perform TWO simultaneous complex FFT's of     |
//|     length 3, then you can choose between two representations:   |
//|      * 1 operand, 3 microvectors, 4 components; storage format is|
//|          given below: [ A0X A0Y B0X B0Y A1X A1Y B1X B1Y ... ]    |
//|          (here A denotes first sequence, B - second one).        |
//|      * 2 operands, 3 microvectors, 2 components; storage format  |
//|          is given below:[A0X A0Y A1X A2Y...B0X B0Y B1X B1Y...]   |
//| Most FFT operations are supported only for the second format, but|
//| you should remember that first format sometimes can be used too. |
//| SUPPORTED OPERATIONS:                                            |
//| row[0]=0:                                                        |
//|  *"end of plan/subplan"                                        |
//|   * in case we meet entry with such type, FFT transformation is  |
//|     finished (or we return from recursive FFT subplan, in case   |
//|     it was subplan).                                             |
//| row[0]=+1:                                                       |
//|  *"reference 1D complex FFT"                                   |
//|   * we perform reference O(N^2) complex FFT on input data, which |
//|     are treated as row[1] arrays, each of row[2] complex numbers,|
//|     and row[3] must be equal to 2                                |
//|   * transformation is performed using temporary buffer           |
//| row[0]=opBluesteinsFFT:                                          |
//|   * input array is handled with Bluestein's algorithm (by        |
//|     zero-padding to Param0 complex numbers).                     |
//|   * this plan calls Param0-point subplan which is located at     |
//|     offset Param1 (offset is measured with respect to location   |
//|     of the calling entry)                                        |
//|   * this plan uses precomputed quantities stored in Plan.PrecR   |
//|     at offset Param2.                                            |
//|   * transformation is performed using 4 temporary buffers, which |
//|     are retrieved from Plan.BluesteinPool.                       |
//| row[0]=+3:                                                       |
//|  *"optimized 1D complex FFT"                                   |
//|   * this function supports only several operand sizes:           |
//|     from 1 to 5.                                                 |
//| These transforms are hard-coded and performed very efficiently   |
//| row[0]=opRadersFFT:                                              |
//|   * input array is handled with Rader's algorithm (permutation   |
//|     and reduction to N-1-point FFT)                              |
//|   * this plan calls N-1-point subplan which is located at        |
//|     offset Param0 (offset is measured with respect to location   |
//|     of the calling entry)                                        |
//|   * this plan uses precomputed primitive root and its inverse    |
//|     (modulo N) which are stored in Param1 and Param2.            |
//|   * Param3 stores offset of the precomputed data for the plan    |
//|   * plan length must be prime, (N-1)*(N-1) must fit into integer |
//|     variable                                                     |
//| row[0]=-1                                                        |
//|  *"complex transposition"                                      |
//|   * input data are treated as row[1] independent arrays, which   |
//|     are processed separately                                     |
//|   * each of operands is treated as matrix with row[4] rows and   |
//|     row[2]/row[4] columns. Each element of the matrix is         |
//|     microvector with row[3] components.                          |
//|   * transposition is performed using temporary buffer            |
//| row[0]=-2                                                        |
//|  *"multiplication by twiddle factors of complex FFT"           |
//|   * input data are treated as row[1] independent arrays, which   |
//|     are processed separately                                     |
//|  *row[4] contains N1-length of the "first FFT" in a          |
//|     Cooley-Tukey FFT algorithm                                   |
//|   * this function does not require temporary buffers             |
//| row[0]=-3                                                        |
//|  *"start of the plan"                                          |
//|   * each subplan must start from this entry                      |
//|   * param0 is ignored                                            |
//|   * param1 stores approximate (optimistic) estimate of KFLOPs    |
//|     required to transform one operand of the plan. Total cost of |
//|     the plan is approximately equal to row[1]*param1 KFLOPs.     |
//|   * this function does not require temporary buffers             |
//| row[0]=-4                                                        |
//|  *"jump"                                                       |
//|   * param0 stores relative offset of the jump site               |
//|     (+1 corresponds to the next entry)                           |
//| row[0]=-5                                                        |
//|  *"parallel call"                                              |
//|   * input data are treated as row[1] independent arrays          |
//|   * child subplan is applied independently for each of arrays -  |
//|     row[1] times                                                 |
//|   * subplan length must be equal to row[2]*row[3]                |
//|   * param0 stores relative offset of the child subplan site      |
//|     (+1 corresponds to the next entry)                           |
//|   * param1 stores approximate total cost of plan, measured in    |
//|     UNITS (1 UNIT = 100 KFLOPs). Plan cost must be rounded DOWN  |
//|     to nearest integer.                                          |
//+------------------------------------------------------------------+
struct CFtPlan
  {
   //--- arrays
   CMatrixInt        m_entries;
   CRowDouble        m_buffer;
   CRowDouble        m_precr;
   CRowDouble        m_preci;
   CRowDouble        m_bluesteinpool[];
   //--- constructor, destructor
                     CFtPlan(void) {}
                    ~CFtPlan(void) {}
   //---
   void              Copy(CFtPlan &obj);
   //--- overloading
   void              operator=(CFtPlan &obj) { Copy(obj); }
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CFtPlan::Copy(CFtPlan &obj)
  {
   m_entries=obj.m_entries;
   m_buffer=obj.m_buffer;
   m_precr=obj.m_precr;
   m_preci=obj.m_preci;
  }
//+------------------------------------------------------------------+
//| Generation FFT, FHT plans                                        |
//+------------------------------------------------------------------+
class CFtBase
  {
public:
   //--- class constants
   static const int  m_coltype;
   static const int  m_coloperandscnt;
   static const int  m_coloperandsize;
   static const int  m_colmicrovectorsize;
   static const int  m_colparam0;
   static const int  m_colparam1;
   static const int  m_colparam2;
   static const int  m_colparam3;
   static const int  m_colscnt;
   static const int  m_opend;
   static const int  m_opcomplexreffft;
   static const int  m_opbluesteinsfft;
   static const int  m_opcomplexcodeletfft;
   static const int  m_opcomplexcodelettwfft;
   static const int  m_opradersfft;
   static const int  m_opcomplextranspose;
   static const int  m_opcomplexfftfactors;
   static const int  m_opstart;
   static const int  m_opjmp;
   static const int  m_opparallelcall;
   static const int  m_MaxRadix;
   static const int  m_updatetw;
   static const int  m_recursivethreshold;
   static const int  m_raderthreshold;
   static const int  m_ftbasecodeletrecommended;
   static const double m_ftbaseinefficiencyfactor;
   static const int  m_ftbasemaxsmoothfactor;
   //--- public methods
   static void       FtComplexFFTPlan(int n,int k,CFtPlan &plan);
   static void       FtApplyPlan(CFtPlan &plan,double &a[],int offsa,int repcnt);
   static void       FtApplyPlan(CFtPlan &plan,CRowDouble &a,int offsa,int repcnt);

   static void       FtBaseFactorize(const int n,const int tasktype,int &n1,int &n2);
   static bool       FtBaseIsSmooth(int n);
   static int        FtBaseFindSmooth(const int n);
   static int        FtBaseFindSmoothEven(const int n);
   static double     FtBaseGetFlopEstimate(const int n);

private:
   static void       FtDetermineSpaceRequirements(int n,int &precrsize,int &precisize);
   static void       FtComplexFFTPlanRec(int n,int k,bool childplan,bool topmostplan,int &rowptr,int &bluesteinsize,int &precrptr,int &preciptr,CFtPlan &plan);
   static void       FtPushEntry(CFtPlan &plan,int &rowptr,int etype,int eopcnt,int eopsize,int emcvsize,int eparam0);
   static void       FtPushEntry2(CFtPlan &plan,int &rowptr,int etype,int eopcnt,int eopsize,int emcvsize,int eparam0,int eparam1);
   static void       FtPushEntry4(CFtPlan &plan,int &rowptr,int etype,int eopcnt,int eopsize,int emcvsize,int eparam0,int eparam1,int eparam2,int eparam3);
   static void       FtApplySubPlan(CFtPlan &plan,int subplan,CRowDouble &a,int abase,int aoffset,CRowDouble &buf,int repcnt);
   static void       FtApplyComplexRefFFT(CRowDouble &a,int offs,int operandscnt,int operandsize,int microvectorsize,CRowDouble &buf);
   static void       FtApplyComplexCodeLetFFT(CRowDouble &a,int offs,int operandscnt,int operandsize,int microvectorsize);
   static void       FtApplyComplexCodeLetTwFFT(CRowDouble &a,int offs,int operandscnt,int operandsize,int microvectorsize);
   static void       FtPrecomputeBluesteinsFFT(int n,int m,CRowDouble &precr,int offs);
   static void       FtBluesteinsFFT(CFtPlan &plan,CRowDouble &a,int abase,int aoffset,int operandscnt,int n,int m,int precoffs,int subplan,CRowDouble &bufa,CRowDouble &bufb,CRowDouble &bufc,CRowDouble &bufd);
   static void       FtPrecomputeRadersFFT(int n,int rq,int riq,CRowDouble &precr,int offs);
   static void       FtRadersFFT(CFtPlan &plan,CRowDouble &a,int abase,int aoffset,int operandscnt,int n,int subplan,int rq,int riq,int precoffs,CRowDouble &buf);
   static void       FtFactorize(int n,bool IsRoot,int &n1,int &n2);
   static int        FtOptimisticEstimate(int n);
   static void       FFtTwCalc(CRowDouble &a,const int aoffset,const int n1,const int n2);
   static void       InternalComplexLinTranspose(CRowDouble &a,const int m,const int n,const int astart,CRowDouble &buf);
   static void       InternalRealLinTranspose(CRowDouble &a,const int m,const int n,const int astart,CRowDouble &buf);
   static void       FFtICLTRec(CRowDouble &a,const int astart,const int astride,CRowDouble &b,const int bstart,const int bstride,const int m,const int n);
   static void       FFtIRLTRec(CRowDouble &a,const int astart,const int astride,CRowDouble &b,const int bstart,const int bstride,const int m,const int n);
   static void       FtBaseFindSmoothRec(const int n,const int seed,const int leastfactor,int &best);
  };
//+------------------------------------------------------------------+
//| Initialize constants                                             |
//+------------------------------------------------------------------+
const int CFtBase::m_coltype=0;
const int CFtBase::m_coloperandscnt=1;
const int CFtBase::m_coloperandsize=2;
const int CFtBase::m_colmicrovectorsize=3;
const int CFtBase::m_colparam0=4;
const int CFtBase::m_colparam1=5;
const int CFtBase::m_colparam2=6;
const int CFtBase::m_colparam3=7;
const int CFtBase::m_colscnt=8;
const int CFtBase::m_opend=0;
const int CFtBase::m_opcomplexreffft=1;
const int CFtBase::m_opbluesteinsfft=2;
const int CFtBase::m_opcomplexcodeletfft=3;
const int CFtBase::m_opcomplexcodelettwfft=4;
const int CFtBase::m_opradersfft=5;
const int CFtBase::m_opcomplextranspose=-1;
const int CFtBase::m_opcomplexfftfactors=-2;
const int CFtBase::m_opstart=-3;
const int CFtBase::m_opjmp=-4;
const int CFtBase::m_opparallelcall=-5;
const int CFtBase::m_MaxRadix=6;
const int CFtBase::m_updatetw=16;
const int CFtBase::m_recursivethreshold=1024;
const int CFtBase::m_raderthreshold=19;
const int CFtBase::m_ftbasecodeletrecommended=5;
const double CFtBase::m_ftbaseinefficiencyfactor=1.3;
const int CFtBase::m_ftbasemaxsmoothfactor=5;
//+------------------------------------------------------------------+
//| This subroutine generates FFT plan for K complex FFT's with      |
//| length N each.                                                   |
//| INPUT PARAMETERS:                                                |
//|   N        -  FFT length (in complex numbers), N>=1              |
//|   K        -  number of repetitions, K>=1                        |
//| OUTPUT PARAMETERS:                                               |
//|   Plan     -  plan                                               |
//+------------------------------------------------------------------+
void CFtBase::FtComplexFFTPlan(int n,int k,CFtPlan &plan)
  {
//--- create variables
   int rowptr=0;
   int bluesteinsize=0;
   int precrptr=0;
   int preciptr=0;
   int precrsize=0;
   int precisize=0;
//--- Initial check for parameters
   if(!CAp::Assert(n>0,__FUNCTION__": N<=0"))
      return;
   if(!CAp::Assert(k>0,__FUNCTION__": K<=0"))
      return;
//--- Determine required sizes of precomputed real and integer
//--- buffers. This stage of code is highly dependent on internals
//--- of FTComplexFFTPlanRec() and must be kept synchronized with
//--- possible changes in internals of plan generation function.
//--- Buffer size is determined as follows:
//--- * N is factorized
//--- * we factor out anything which is less or equal to MaxRadix
//--- * prime factor F>RaderThreshold requires 4*FTBaseFindSmooth(2*F-1)
//---   real entries to store precomputed Quantities for Bluestein's
//---   transformation
//--- * prime factor F<=RaderThreshold does NOT require
//---   precomputed storage
   FtDetermineSpaceRequirements(n,precrsize,precisize);
   if(precrsize>0)
      plan.m_precr.Resize(precrsize);
   if(precisize>0)
      plan.m_preci.Resize(precisize);
//--- Generate plan
   bluesteinsize=1;
   plan.m_buffer.Resize(2*n*k);
   FtComplexFFTPlanRec(n,k,true,true,rowptr,bluesteinsize,precrptr,preciptr,plan);
   ArrayResize(plan.m_bluesteinpool,1);
   plan.m_bluesteinpool[0]=vector<double>::Zeros(bluesteinsize);
//--- Check that actual amount of precomputed space used by transformation
//--- plan is EXACTLY equal to amount of space allocated by us.
   if(!CAp::Assert(precrptr==precrsize,__FUNCTION__": internal error (PrecRPtr<>PrecRSize)"))
      return;
   if(!CAp::Assert(preciptr==precisize,__FUNCTION__": internal error (PrecRPtr<>PrecRSize)"))
      return;
  }
//+------------------------------------------------------------------+
//| This subroutine applies transformation plan to input/output      |
//| array A.                                                         |
//| INPUT PARAMETERS:                                                |
//|   Plan     -  transformation plan                                |
//|   A        -  array, must be large enough for plan to work       |
//|   OffsA    -  offset of the subarray to process                  |
//|   RepCnt   -  repetition count (transformation is repeatedly     |
//|               applied to subsequent subarrays)                   |
//| OUTPUT PARAMETERS:                                               |
//|   Plan     -  plan (temporary buffers can be modified, plan      |
//|               itself is unchanged and can be reused)             |
//|   A        -  transformed array                                  |
//+------------------------------------------------------------------+
void CFtBase::FtApplyPlan(CFtPlan &plan,double &a[],int offsa,int repcnt)
  {
   CRowDouble A=a;
   FtApplyPlan(plan,A,offsa,repcnt);
   A.ToArray(a);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CFtBase::FtApplyPlan(CFtPlan &plan,CRowDouble &a,int offsa,int repcnt)
  {
//--- create variables
   int i=0;
   int plansize=plan.m_entries.Get(0,m_coloperandscnt)*plan.m_entries.Get(0,m_coloperandsize) *
                  plan.m_entries.Get(0,m_colmicrovectorsize);

   for(i=0; i<repcnt; i++)
      FtApplySubPlan(plan,0,a,offsa+plansize*i,0,plan.m_buffer,1);
  }
//+------------------------------------------------------------------+
//| Returns good factorization N=N1*N2.                              |
//| Usually N1<=N2 (but not always - small N's may be exception).    |
//| if N1<>1 then N2<>1.                                             |
//| Factorization is chosen depending on task type and codelets we   |
//| have.                                                            |
//+------------------------------------------------------------------+
void CFtBase::FtBaseFactorize(const int n,const int tasktype,
                              int &n1,int &n2)
  {
//--- create a variable
   int j=0;
//--- initialization
   n1=0;
   n2=0;
//--- try to find good codelet
   if(n1*n2!=n)
     {
      for(j=m_ftbasecodeletrecommended; j>=2; j--)
        {
         //--- check
         if(n%j==0)
           {
            n1=j;
            n2=n/j;
            break;
           }
        }
     }
//--- try to factorize N
   if(n1*n2!=n)
     {
      for(j=m_ftbasecodeletrecommended+1; j<n; j++)
        {
         //--- check
         if(n%j==0)
           {
            n1=j;
            n2=n/j;
            break;
           }
        }
     }
//--- looks like N is prime :(
   if(n1*n2!=n)
     {
      n1=1;
      n2=n;
     }
//--- normalize
   if(n2==1 && n1!=1)
     {
      n2=n1;
      n1=1;
     }
  }
//+------------------------------------------------------------------+
//| Is number smooth?                                                |
//+------------------------------------------------------------------+
bool CFtBase::FtBaseIsSmooth(int n)
  {
//--- change n
   for(int i=2; i<=m_ftbasemaxsmoothfactor; i++)
     {
      while(n%i==0)
         n=n/i;
     }
//--- check
   if(n==1)
      return(true);
//--- return result
   return(false);
  }
//+------------------------------------------------------------------+
//| Returns smallest smooth (divisible only by 2, 3, 5) number that  |
//| is greater than or equal to max(N,2)                             |
//+------------------------------------------------------------------+
int CFtBase::FtBaseFindSmooth(const int n)
  {
//--- create a variable
   int best=2;
//--- calculation
   while(best<n)
      best=2*best;
//--- function call
   FtBaseFindSmoothRec(n,1,2,best);
//--- return result
   return(best);
  }
//+------------------------------------------------------------------+
//| Returns smallest smooth (divisible only by 2, 3, 5) even number  |
//| that is greater than or equal to max(N,2)                        |
//+------------------------------------------------------------------+
int CFtBase::FtBaseFindSmoothEven(const int n)
  {
//--- create a variable
   int best=2;
//--- calculation
   while(best<n)
      best=2*best;
//--- function call
   FtBaseFindSmoothRec(n,2,2,best);
//--- return result
   return(best);
  }
//+------------------------------------------------------------------+
//| Returns estimate of FLOP count for the FFT.                      |
//| It is only an estimate based on operations count for the PERFECT |
//| FFT and relative inefficiency of the algorithm actually used.    |
//| N should be power of 2, estimates are badly wrong for            |
//| non-power-of-2 N's.                                              |
//+------------------------------------------------------------------+
double CFtBase::FtBaseGetFlopEstimate(const int n)
  {
   return(m_ftbaseinefficiencyfactor*(4*n*MathLog(n)/MathLog(2)-6*n+8));
  }
//+------------------------------------------------------------------+
//| This function returns EXACT estimate of the space requirements   |
//| for N-point FFT. Internals of this function are highly dependent |
//| on details of different FFTs employed by this unit, so every time|
//| algorithm is changed this function has to be rewritten.          |
//| INPUT PARAMETERS:                                                |
//|   N         - transform length                                   |
//|   PrecRSize - must be set to zero                                |
//|   PrecISize - must be set to zero                                |
//| OUTPUT PARAMETERS:                                               |
//|   PrecRSize - number of real temporaries required for            |
//|               transformation                                     |
//|   PrecISize - number of integer temporaries required for         |
//|               transformation                                     |
//+------------------------------------------------------------------+
void CFtBase::FtDetermineSpaceRequirements(int n,int &precrsize,int &precisize)
  {
//--- create variables
   int ncur=0;
   int f=0;
//--- Determine required sizes of precomputed real and integer
//--- buffers. This stage of code is highly dependent on internals
//--- of FTComplexFFTPlanRec() and must be kept synchronized with
//--- possible changes in internals of plan generation function.
//--- Buffer size is determined as follows:
//--- * N is factorized
//--- * we factor out anything which is less or equal to MaxRadix
//--- * prime factor F>RaderThreshold requires 4*FTBaseFindSmooth(2*F-1)
//---   real entries to store precomputed Quantities for Bluestein's
//---   transformation
//--- * prime factor F<=RaderThreshold requires 2*(F-1)+ESTIMATE(F-1)
//---   precomputed storage
   ncur=n;
   for(int i=2; i<=m_MaxRadix; i++)
     {
      while(ncur%i==0)
         ncur=ncur/i;
     }
   f=2;
   while(f<=ncur)
     {
      while(ncur%f==0)
        {
         if(f>m_raderthreshold)
            precrsize+=4*FtBaseFindSmooth(2*f-1);
         else
           {
            precrsize+=2*(f-1);
            FtDetermineSpaceRequirements(f-1,precrsize,precisize);
           }
         ncur=ncur/f;
        }
      f++;
     }
  }
//+------------------------------------------------------------------+
//| Recurrent function called by FTComplexFFTPlan() and other        |
//| functions. It recursively builds transformation plan             |
//| INPUT PARAMETERS:                                                |
//|   N           -  FFT length (in complex numbers), N>=1           |
//|   K           -  number of repetitions, K>=1                     |
//|   ChildPlan   -  if True, plan generator inserts OpStart/opEnd   |
//|                  in the plan header/footer.                      |
//|   TopmostPlan -  if True, plan generator assumes that it is      |
//|                  topmost plan:                                   |
//|                  * it may use global buffer for transpositions   |
//|                    and there is no other plan which executes in  |
//|                    parallel                                      |
//|   RowPtr      -  index which points to past-the-last entry       |
//|                  generated so far                                |
//|   BluesteinSize- amount of storage (in real numbers) required    |
//|                  for Bluestein buffer                            |
//|   PrecRPtr    -  pointer to unused part of precomputed real      |
//|                  buffer (Plan.PrecR):                            |
//|                  * when this function stores some data to        |
//|                    precomputed buffer, it advances pointer.      |
//|                  * it is responsibility of the function to assert|
//|                    that Plan.PrecR has enough space to store data|
//|                    before actually writing to buffer.            |
//|                  * it is responsibility of the caller to allocate|
//|                    enough space before calling this function     |
//|   PrecIPtr    -  pointer to unused part of precomputed integer   |
//|                  buffer (Plan.PrecI):                            |
//|                  * when this function stores some data to        |
//|                    precomputed buffer, it advances pointer.      |
//|                  * it is responsibility of the function to assert|
//|                    that Plan.PrecR has enough space to store data|
//|                    before actually writing to buffer.            |
//|                  * it is responsibility of the caller to allocate|
//|                    enough space before calling this function     |
//|   Plan        -  plan (generated so far)                         |
//| OUTPUT PARAMETERS:                                               |
//|   RowPtr      -  updated pointer (advanced by number of entries  |
//|                  generated by function)                          |
//|   BluesteinSize- updated amount (may be increased, but may never |
//|                  be decreased)                                   |
//| NOTE: in case TopmostPlan is True, ChildPlan is also must be True|
//+------------------------------------------------------------------+
void CFtBase::FtComplexFFTPlanRec(int n,int k,bool childplan,
                                  bool topmostplan,int &rowptr,
                                  int &bluesteinsize,int &precrptr,
                                  int &preciptr,CFtPlan &plan)
  {
//--- create variables
   CRowDouble localbuf;
   int m=0;
   int n1=0;
   int n2=0;
   int gq=0;
   int giq=0;
   int row0=0;
   int row1=0;
   int row2=0;
   int row3=0;
//--- check
   if(!CAp::Assert(n>0,__FUNCTION__": N<=0"))
      return;
   if(!CAp::Assert(k>0,__FUNCTION__": K<=0"))
      return;
   if(!CAp::Assert(!topmostplan || childplan,__FUNCTION__": ChildPlan is inconsistent with TopmostPlan"))
      return;
//--- Try to generate "topmost" plan
   if(topmostplan && n>m_recursivethreshold)
     {
      FtFactorize(n,false,n1,n2);
      if(n1*n2==0)
        {
         //--- Handle prime-factor FFT with Bluestein's FFT.
         //--- Determine size of Bluestein's buffer.
         m=FtBaseFindSmooth(2*n-1);
         bluesteinsize=MathMax(2*m,bluesteinsize);
         //--- Generate plan
         FtPushEntry2(plan,rowptr,m_opstart,k,n,2,-1,FtOptimisticEstimate(n));
         FtPushEntry4(plan,rowptr,m_opbluesteinsfft,k,n,2,m,2,precrptr,0);
         row0=rowptr;
         FtPushEntry(plan,rowptr,m_opjmp,0,0,0,0);
         FtComplexFFTPlanRec(m,1,true,true,rowptr,bluesteinsize,precrptr,preciptr,plan);
         row1=rowptr;
         plan.m_entries.Set(row0,m_colparam0,row1-row0);
         FtPushEntry(plan,rowptr,m_opend,k,n,2,0);
         //--- Fill precomputed buffer
         FtPrecomputeBluesteinsFFT(n,m,plan.m_precr,precrptr);
         //--- Update pointer to the precomputed area
         precrptr+=4*m;
        }
      else
        {
         //--- Handle composite FFT with recursive Cooley-Tukey which
         //--- uses global buffer instead of local one.
         FtPushEntry2(plan,rowptr,m_opstart,k,n,2,-1,FtOptimisticEstimate(n));
         FtPushEntry(plan,rowptr,m_opcomplextranspose,k,n,2,n1);
         row0=rowptr;
         FtPushEntry2(plan,rowptr,m_opparallelcall,k*n2,n1,2,0,FtOptimisticEstimate(n));
         FtPushEntry(plan,rowptr,m_opcomplexfftfactors,k,n,2,n1);
         FtPushEntry(plan,rowptr,m_opcomplextranspose,k,n,2,n2);
         row2=rowptr;
         FtPushEntry2(plan,rowptr,m_opparallelcall,k*n1,n2,2,0,FtOptimisticEstimate(n));
         FtPushEntry(plan,rowptr,m_opcomplextranspose,k,n,2,n1);
         FtPushEntry(plan,rowptr,m_opend,k,n,2,0);
         row1=rowptr;
         FtComplexFFTPlanRec(n1,1,true,false,rowptr,bluesteinsize,precrptr,preciptr,plan);
         plan.m_entries.Set(row0,m_colparam0,row1-row0);
         row3=rowptr;
         FtComplexFFTPlanRec(n2,1,true,false,rowptr,bluesteinsize,precrptr,preciptr,plan);
         plan.m_entries.Set(row2,m_colparam0,row3-row2);
        }
      return;
     }
//--- Prepare "non-topmost" plan:
//--- * calculate factorization
//--- * use local (shared) buffer
//--- * update buffer size - ANY plan will need at least
//---   2*N temporaries, additional requirements can be
//---   applied later
   FtFactorize(n,false,n1,n2);
//--- Handle FFT's with N1*N2=0: either small-N or prime-factor
   if(n1*n2==0)
     {
      if(n<=m_MaxRadix)
        {
         //--- Small-N FFT
         if(childplan)
            FtPushEntry2(plan,rowptr,m_opstart,k,n,2,-1,FtOptimisticEstimate(n));
         FtPushEntry(plan,rowptr,m_opcomplexcodeletfft,k,n,2,0);
         if(childplan)
            FtPushEntry(plan,rowptr,m_opend,k,n,2,0);
         return;
        }
      if(n<=m_raderthreshold)
        {
         //--- Handle prime-factor FFT's with Rader's FFT
         m=n-1;
         if(childplan)
            FtPushEntry2(plan,rowptr,m_opstart,k,n,2,-1,FtOptimisticEstimate(n));
         CNTheory::FindPrimitiveRootAndInverse(n,gq,giq);
         FtPushEntry4(plan,rowptr,m_opradersfft,k,n,2,2,gq,giq,precrptr);
         FtPrecomputeRadersFFT(n,gq,giq,plan.m_precr,precrptr);
         precrptr+=2*(n-1);
         row0=rowptr;
         FtPushEntry(plan,rowptr,m_opjmp,0,0,0,0);
         FtComplexFFTPlanRec(m,1,true,false,rowptr,bluesteinsize,precrptr,preciptr,plan);
         row1=rowptr;
         plan.m_entries.Set(row0,m_colparam0,row1-row0);
         if(childplan)
            FtPushEntry(plan,rowptr,m_opend,k,n,2,0);
        }
      else
        {
         //--- Handle prime-factor FFT's with Bluestein's FFT
         m=FtBaseFindSmooth(2*n-1);
         bluesteinsize=MathMax(2*m,bluesteinsize);
         if(childplan)
            FtPushEntry2(plan,rowptr,m_opstart,k,n,2,-1,FtOptimisticEstimate(n));
         FtPushEntry4(plan,rowptr,m_opbluesteinsfft,k,n,2,m,2,precrptr,0);
         FtPrecomputeBluesteinsFFT(n,m,plan.m_precr,precrptr);
         precrptr+=4*m;
         row0=rowptr;
         FtPushEntry(plan,rowptr,m_opjmp,0,0,0,0);
         FtComplexFFTPlanRec(m,1,true,false,rowptr,bluesteinsize,precrptr,preciptr,plan);
         row1=rowptr;
         plan.m_entries.Set(row0,m_colparam0,row1-row0);
         if(childplan)
            FtPushEntry(plan,rowptr,m_opend,k,n,2,0);
        }
      return;
     }
//--- Handle Cooley-Tukey FFT with small N1
   if(n1<=m_MaxRadix)
     {
      //--- Specialized transformation for small N1:
      //--- * N2 short inplace FFT's, each N1-point, with integrated twiddle factors
      //--- * N1 long FFT's
      //--- * final transposition
      if(childplan)
         FtPushEntry2(plan,rowptr,m_opstart,k,n,2,-1,FtOptimisticEstimate(n));
      FtPushEntry(plan,rowptr,m_opcomplexcodelettwfft,k,n1,2*n2,0);
      FtComplexFFTPlanRec(n2,k*n1,false,false,rowptr,bluesteinsize,precrptr,preciptr,plan);
      FtPushEntry(plan,rowptr,m_opcomplextranspose,k,n,2,n1);
      if(childplan)
         FtPushEntry(plan,rowptr,m_opend,k,n,2,0);
      return;
     }
//--- Handle general Cooley-Tukey FFT, either "flat" or "recursive"
   if(n<=m_recursivethreshold)
     {
      //--- General code for large N1/N2, "flat" version without explicit recurrence
      //--- (nested subplans are inserted directly into the body of the plan)
      if(childplan)
         FtPushEntry2(plan,rowptr,m_opstart,k,n,2,-1,FtOptimisticEstimate(n));
      FtPushEntry(plan,rowptr,m_opcomplextranspose,k,n,2,n1);
      FtComplexFFTPlanRec(n1,k*n2,false,false,rowptr,bluesteinsize,precrptr,preciptr,plan);
      FtPushEntry(plan,rowptr,m_opcomplexfftfactors,k,n,2,n1);
      FtPushEntry(plan,rowptr,m_opcomplextranspose,k,n,2,n2);
      FtComplexFFTPlanRec(n2,k*n1,false,false,rowptr,bluesteinsize,precrptr,preciptr,plan);
      FtPushEntry(plan,rowptr,m_opcomplextranspose,k,n,2,n1);
      if(childplan)
         FtPushEntry(plan,rowptr,m_opend,k,n,2,0);
     }
   else
     {
      //--- General code for large N1/N2, "recursive" version - nested subplans
      //--- are separated from the plan body.
      //--- Generate parent plan.
      if(childplan)
         FtPushEntry2(plan,rowptr,m_opstart,k,n,2,-1,FtOptimisticEstimate(n));
      FtPushEntry(plan,rowptr,m_opcomplextranspose,k,n,2,n1);
      row0=rowptr;
      FtPushEntry2(plan,rowptr,m_opparallelcall,k*n2,n1,2,0,FtOptimisticEstimate(n));
      FtPushEntry(plan,rowptr,m_opcomplexfftfactors,k,n,2,n1);
      FtPushEntry(plan,rowptr,m_opcomplextranspose,k,n,2,n2);
      row2=rowptr;
      FtPushEntry2(plan,rowptr,m_opparallelcall,k*n1,n2,2,0,FtOptimisticEstimate(n));
      FtPushEntry(plan,rowptr,m_opcomplextranspose,k,n,2,n1);
      if(childplan)
         FtPushEntry(plan,rowptr,m_opend,k,n,2,0);
      //--- Generate child subplans, insert refence to parent plans
      row1=rowptr;
      FtComplexFFTPlanRec(n1,1,true,false,rowptr,bluesteinsize,precrptr,preciptr,plan);
      plan.m_entries.Set(row0,m_colparam0,row1-row0);
      row3=rowptr;
      FtComplexFFTPlanRec(n2,1,true,false,rowptr,bluesteinsize,precrptr,preciptr,plan);
      plan.m_entries.Set(row2,m_colparam0,row3-row2);
     }
  }
//+------------------------------------------------------------------+
//| This function pushes one more entry to the plan. It resizes      |
//| Entries matrix if needed.                                        |
//| INPUT PARAMETERS:                                                |
//|   Plan     -  plan (generated so far)                            |
//|   RowPtr   -  index which points to past-the-last entry          |
//|               generated so far                                   |
//|   EType    -  entry type                                         |
//|   EOpCnt   -  operands count                                     |
//|   EOpSize  -  operand size                                       |
//|   EMcvSize -  microvector size                                   |
//|   EParam0  -  parameter 0                                        |
//| OUTPUT PARAMETERS:                                               |
//|   Plan     -  updated plan                                       |
//|   RowPtr   -  updated pointer                                    |
//| NOTE: Param1 is set to -1.                                       |
//+------------------------------------------------------------------+
void CFtBase::FtPushEntry(CFtPlan &plan,int &rowptr,int etype,int eopcnt,
                          int eopsize,int emcvsize,int eparam0)
  {
   FtPushEntry2(plan,rowptr,etype,eopcnt,eopsize,emcvsize,eparam0,-1);
  }
//+------------------------------------------------------------------+
//| Same as FTPushEntry(), but sets Param0 AND Param1.               |
//| This function pushes one more entry to the plan. It resized      |
//| Entries matrix if needed.                                        |
//| INPUT PARAMETERS:                                                |
//|   Plan        -  plan (generated so far)                         |
//|   RowPtr      -  index which points to past-the-last entry       |
//|                  generated so far                                |
//|   EType       -  entry type                                      |
//|   EOpCnt      -  operands count                                  |
//|   EOpSize     -  operand size                                    |
//|   EMcvSize    -  microvector size                                |
//|   EParam0     -  parameter 0                                     |
//|   EParam1     -  parameter 1                                     |
//| OUTPUT PARAMETERS:                                               |
//|   Plan        -  updated plan                                    |
//|   RowPtr      -  updated pointer                                 |
//+------------------------------------------------------------------+
void CFtBase::FtPushEntry2(CFtPlan &plan,int &rowptr,int etype,
                           int eopcnt,int eopsize,int emcvsize,
                           int eparam0,int eparam1)
  {
   if(rowptr>=plan.m_entries.Rows())
      plan.m_entries.Resize(MathMax(2*plan.m_entries.Rows(),1),m_colscnt);
   plan.m_entries.Set(rowptr,m_coltype,etype);
   plan.m_entries.Set(rowptr,m_coloperandscnt,eopcnt);
   plan.m_entries.Set(rowptr,m_coloperandsize,eopsize);
   plan.m_entries.Set(rowptr,m_colmicrovectorsize,emcvsize);
   plan.m_entries.Set(rowptr,m_colparam0,eparam0);
   plan.m_entries.Set(rowptr,m_colparam1,eparam1);
   plan.m_entries.Set(rowptr,m_colparam2,0);
   plan.m_entries.Set(rowptr,m_colparam3,0);
   rowptr++;
  }
//+------------------------------------------------------------------+
//| Same as FTPushEntry(), but sets Param0, Param1, Param2 and Param3|
//| This function pushes one more entry to the plan. It resized      |
//| Entries matrix if needed.                                        |
//| INPUT PARAMETERS:                                                |
//|   Plan        -  plan (generated so far)                         |
//|   RowPtr      -  index which points to past-the-last entry       |
//|                  generated so far                                |
//|   EType       -  entry type                                      |
//|   EOpCnt      -  operands count                                  |
//|   EOpSize     -  operand size                                    |
//|   EMcvSize    -  microvector size                                |
//|   EParam0     -  parameter 0                                     |
//|   EParam1     -  parameter 1                                     |
//|   EParam2     -  parameter 2                                     |
//|   EParam3     -  parameter 3                                     |
//| OUTPUT PARAMETERS:                                               |
//|   Plan        -  updated plan                                    |
//|   RowPtr      -  updated pointer                                 |
//+------------------------------------------------------------------+
void CFtBase::FtPushEntry4(CFtPlan &plan,int &rowptr,int etype,
                           int eopcnt,int eopsize,int emcvsize,
                           int eparam0,int eparam1,int eparam2,
                           int eparam3)
  {
//--- check
   if(rowptr>=plan.m_entries.Rows())
      plan.m_entries.Resize(MathMax(2*plan.m_entries.Rows(),1),m_colscnt);
   plan.m_entries.Set(rowptr,m_coltype,etype);
   plan.m_entries.Set(rowptr,m_coloperandscnt,eopcnt);
   plan.m_entries.Set(rowptr,m_coloperandsize,eopsize);
   plan.m_entries.Set(rowptr,m_colmicrovectorsize,emcvsize);
   plan.m_entries.Set(rowptr,m_colparam0,eparam0);
   plan.m_entries.Set(rowptr,m_colparam1,eparam1);
   plan.m_entries.Set(rowptr,m_colparam2,eparam2);
   plan.m_entries.Set(rowptr,m_colparam3,eparam3);
   rowptr++;
  }
//+------------------------------------------------------------------+
//| This subroutine applies subplan to input/output array A.         |
//| INPUT PARAMETERS:                                                |
//|   Plan        -  transformation plan                             |
//|   SubPlan     -  subplan index                                   |
//|   A           -  array, must be large enough for plan to work    |
//|   ABase       -  base offset in array A, this value points to    |
//|                  start of subarray whose length is equal to      |
//|                  length of the plan                              |
//|   AOffset     -  offset with respect to ABase,                   |
//|                  0<=AOffset<PlanLength. This is an offset within |
//|                  large PlanLength-subarray of the chunk to       |
//|                  process.                                        |
//|   Buf         -  temporary buffer whose length is equal to plan  |
//|                  length (without taking into account RepCnt) or  |
//|                  larger.                                         |
//|   OffsBuf     -  offset in the buffer array                      |
//|   RepCnt      -  repetition count (transformation is repeatedly  |
//|                  applied to subsequent subarrays)                |
//| OUTPUT PARAMETERS:                                               |
//|   Plan        -  plan (temporary buffers can be modified, plan   |
//|                  itself is unchanged and can be reused)          |
//|   A           -  transformed array                               |
//+------------------------------------------------------------------+
void CFtBase::FtApplySubPlan(CFtPlan &plan,int subplan,CRowDouble &a,
                             int abase,int aoffset,CRowDouble &buf,
                             int repcnt)
  {
//--- create variables
   int rowidx=0;
   int i=0;
   int n1=0;
   int n2=0;
   int operation=0;
   int operandscnt=0;
   int operandsize=0;
   int microvectorsize=0;
   int param0=0;
   int param1=0;
   int parentsize=0;
   int childsize=0;
   int m_ChunkSize=0;
   int lastchunksize=0;
   CRowDouble bufa;
   CRowDouble bufb;
   CRowDouble bufc;
   CRowDouble bufd;
//---check
   if(!CAp::Assert(plan.m_entries.Get(subplan,m_coltype)==m_opstart,__FUNCTION__": incorrect subplan header"))
      return;
   rowidx=subplan+1;
   while(plan.m_entries.Get(rowidx,m_coltype)!=m_opend)
     {
      operation=plan.m_entries.Get(rowidx,m_coltype);
      operandscnt=repcnt*plan.m_entries.Get(rowidx,m_coloperandscnt);
      operandsize=plan.m_entries.Get(rowidx,m_coloperandsize);
      microvectorsize=plan.m_entries.Get(rowidx,m_colmicrovectorsize);
      param0=plan.m_entries.Get(rowidx,m_colparam0);
      param1=plan.m_entries.Get(rowidx,m_colparam1);
      //--- Process "jump" operation
      if(operation==m_opjmp)
         rowidx+=plan.m_entries.Get(rowidx,m_colparam0);
      else
        {
         //--- Process "parallel call" operation:
         //--- * we perform initial check for consistency between parent and child plans
         //--- * we call FTSplitAndApplyParallelPlan(), which splits parallel plan into
         //---   several parallel tasks
         if(operation==m_opparallelcall)
           {
            parentsize=operandsize*microvectorsize;
            childsize=plan.m_entries.Get(rowidx+param0,m_coloperandscnt)*plan.m_entries.Get(rowidx+param0,m_coloperandsize) *
                        plan.m_entries.Get(rowidx+param0,m_colmicrovectorsize);
            //--- check
            if(!CAp::Assert(plan.m_entries.Get(rowidx+param0,m_coltype)==m_opstart,__FUNCTION__": incorrect child subplan header"))
               return;
            if(!CAp::Assert(parentsize==childsize,__FUNCTION__": incorrect child subplan header"))
               return;
            m_ChunkSize=MathMax(m_recursivethreshold/childsize,1);
            lastchunksize=operandscnt%m_ChunkSize;
            if(lastchunksize==0)
               lastchunksize=m_ChunkSize;
            i=0;
            while(i<operandscnt)
              {
               m_ChunkSize=MathMin(m_ChunkSize,operandscnt-i);
               FtApplySubPlan(plan,rowidx+param0,a,abase,aoffset+i*childsize,buf,m_ChunkSize);
               i+=m_ChunkSize;
              }
            rowidx++;
           }
         else
           {
            //--- Process "reference complex FFT" operation
            if(operation==m_opcomplexreffft)
              {
               FtApplyComplexRefFFT(a,abase+aoffset,operandscnt,operandsize,microvectorsize,buf);
               rowidx++;
              }
            else
              {
               //--- Process "codelet FFT" operation
               if(operation==m_opcomplexcodeletfft)
                 {
                  FtApplyComplexCodeLetFFT(a,abase+aoffset,operandscnt,operandsize,microvectorsize);
                  rowidx++;
                 }
               else
                 {
                  //--- Process "integrated codelet FFT" operation
                  if(operation==m_opcomplexcodelettwfft)
                    {
                     FtApplyComplexCodeLetTwFFT(a,abase+aoffset,operandscnt,operandsize,microvectorsize);
                     rowidx++;
                    }
                  else
                    {
                     //--- Process Bluestein's FFT operation
                     if(operation==m_opbluesteinsfft)
                       {
                        if(!CAp::Assert(microvectorsize==2,__FUNCTION__": microvectorsize!=2 for Bluesteins FFT"))
                           return;
                        int last=ArraySize(plan.m_bluesteinpool)-1;
                        if(!CAp::Assert(last>=0,__FUNCTION__": Bluesteins FFT pool empty"))
                           return;
                        bufa=plan.m_bluesteinpool[last];
                        bufb=plan.m_bluesteinpool[last];
                        bufc=plan.m_bluesteinpool[last];
                        bufd=plan.m_bluesteinpool[last];
                        FtBluesteinsFFT(plan,a,abase,aoffset,operandscnt,operandsize,plan.m_entries.Get(rowidx,m_colparam0),
                                        plan.m_entries.Get(rowidx,m_colparam2),rowidx+plan.m_entries.Get(rowidx,m_colparam1),
                                        bufa,bufb,bufc,bufd);
                        if(!CAp::Assert(ArrayResize(plan.m_bluesteinpool,last+5)>0,__FUNCTION__": microvectorsize!=2 for Bluesteins FFT"))
                           return;
                        plan.m_bluesteinpool[last+1]=bufa;
                        plan.m_bluesteinpool[last+2]=bufb;
                        plan.m_bluesteinpool[last+3]=bufc;
                        plan.m_bluesteinpool[last+4]=bufd;
                        rowidx++;
                       }
                     else
                       {
                        //--- Process Rader's FFT
                        if(operation==m_opradersfft)
                          {
                           FtRadersFFT(plan,a,abase,aoffset,operandscnt,operandsize,rowidx+plan.m_entries.Get(rowidx,m_colparam0),
                                       plan.m_entries.Get(rowidx,m_colparam1),plan.m_entries.Get(rowidx,m_colparam2),
                                       plan.m_entries.Get(rowidx,m_colparam3),buf);
                           rowidx++;
                          }
                        else
                          {
                           //--- Process "complex twiddle factors" operation
                           if(operation==m_opcomplexfftfactors)
                             {
                              if(!CAp::Assert(microvectorsize==2,__FUNCTION__": MicrovectorSize<>1"))
                                 return;
                              n1=plan.m_entries.Get(rowidx,m_colparam0);
                              n2=operandsize/n1;
                              for(i=0; i<operandscnt; i++)
                                 FFtTwCalc(a,abase+aoffset+i*operandsize*2,n1,n2);
                              rowidx++;
                             }
                           else
                             {
                              //--- Process "complex transposition" operation
                              if(operation==m_opcomplextranspose)
                                {
                                 if(!CAp::Assert(microvectorsize==2,__FUNCTION__": MicrovectorSize<>1"))
                                    return;
                                 n1=plan.m_entries.Get(rowidx,m_colparam0);
                                 n2=operandsize/n1;
                                 for(i=0; i<operandscnt; i++)
                                    InternalComplexLinTranspose(a,n1,n2,abase+aoffset+i*operandsize*2,buf);
                                 rowidx++;
                                }
                              else
                                {
                                 //--- Error
                                 CAp::Assert(false,__FUNCTION__": unexpected plan type");
                                 return;
                                }
                             }
                          }
                       }
                    }
                 }
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+
//| This subroutine applies complex reference FFT to input/output    |
//| array A.                                                         |
//| VERY SLOW OPERATION, do not use it in real life plans :)         |
//| INPUT PARAMETERS:                                                |
//|   A           -  array, must be large enough for plan to work    |
//|   Offs        -  offset of the subarray to process               |
//|   OperandsCnt -  operands count (see description of              |
//|                  FastTransformPlan)                              |
//|   OperandSize -  operand size (see description of                |
//|                  FastTransformPlan)                              |
//|   MicrovectorSize - microvector size (see description of         |
//|                  FastTransformPlan)                              |
//|   Buf         -  temporary array, must be at least               |
//|                  OperandsCnt * OperandSize * MicrovectorSize     |
//| OUTPUT PARAMETERS:                                               |
//|   A           -  transformed array                               |
//+------------------------------------------------------------------+
void CFtBase::FtApplyComplexRefFFT(CRowDouble &a,int offs,int operandscnt,
                                   int operandsize,int microvectorsize,
                                   CRowDouble &buf)
  {
//--- check
   if(!CAp::Assert(operandscnt>=1,__FUNCTION__": OperandsCnt<1"))
      return;
   if(!CAp::Assert(operandsize>=1,__FUNCTION__": OperandSize<1"))
      return;
   if(!CAp::Assert(microvectorsize==2,__FUNCTION__": MicrovectorSize<>2"))
      return;
//--- create variables
   double hre=0;
   double him=0;
   double c=0;
   double s=0;
   double re=0;
   double im=0;
   int    n=operandsize;
//--- main loop
   for(int opidx=0; opidx<operandscnt; opidx++)
     {
      for(int i=0; i<n; i++)
        {
         hre=0;
         him=0;
         for(int k=0; k<n; k++)
           {
            re=a[offs+opidx*operandsize*2+2*k+0];
            im=a[offs+opidx*operandsize*2+2*k+1];
            c=MathCos(-(2*M_PI*k*i/n));
            s=MathSin(-(2*M_PI*k*i/n));
            hre+=c*re-s*im;
            him+=c*im+s*re;
           }
         buf.Set(2*i+0,hre);
         buf.Set(2*i+1,him);
        }
      for(int i=0; i<operandsize*2; i++)
         a.Set(offs+opidx*operandsize*2+i,buf[i]);
     }
  }
//+------------------------------------------------------------------+
//| This subroutine applies complex codelet FFT to input/output      |
//| array A.                                                         |
//| INPUT PARAMETERS:                                                |
//|   A           -  array, must be large enough for plan to work    |
//|   Offs        -  offset of the subarray to process               |
//|   OperandsCnt -  operands count (see description of              |
//|                  FastTransformPlan)                              |
//|   OperandSize -  operand size (see description of                |
//|                  FastTransformPlan)                              |
//|   MicrovectorSize - microvector size, must be 2                  |
//| OUTPUT PARAMETERS:                                               |
//|   A           -  transformed array                               |
//+------------------------------------------------------------------+
void CFtBase::FtApplyComplexCodeLetFFT(CRowDouble &a,int offs,
                                       int operandscnt,
                                       int operandsize,
                                       int microvectorsize)
  {
//--- check
   if(!CAp::Assert(operandscnt>=1,__FUNCTION__": OperandsCnt<1"))
      return;
   if(!CAp::Assert(operandsize>=1,__FUNCTION__": OperandSize<1"))
      return;
   if(!CAp::Assert(operandsize<=m_MaxRadix,__FUNCTION__": N>MaxRadix"))
      return;
   if(!CAp::Assert(microvectorsize==2,__FUNCTION__": MicrovectorSize<>2"))
      return;
//--- create variables
   int    opidx=0;
   int    aoffset=0;
   double a0x=0;
   double a0y=0;
   double a1x=0;
   double a1y=0;
   double a2x=0;
   double a2y=0;
   double a3x=0;
   double a3y=0;
   double a4x=0;
   double a4y=0;
   double a5x=0;
   double a5y=0;
   double v0=0;
   double v1=0;
   double v2=0;
   double v3=0;
   double t1x=0;
   double t1y=0;
   double t2x=0;
   double t2y=0;
   double t3x=0;
   double t3y=0;
   double t4x=0;
   double t4y=0;
   double t5x=0;
   double t5y=0;
   double m1x=0;
   double m1y=0;
   double m2x=0;
   double m2y=0;
   double m3x=0;
   double m3y=0;
   double m4x=0;
   double m4y=0;
   double m5x=0;
   double m5y=0;
   double s1x=0;
   double s1y=0;
   double s2x=0;
   double s2y=0;
   double s3x=0;
   double s3y=0;
   double s4x=0;
   double s4y=0;
   double s5x=0;
   double s5y=0;
   double c1=0;
   double c2=0;
   double c3=0;
   double c4=0;
   double c5=0;
   double v=0;
   int    n=operandsize;
//--- Hard-coded transforms for different N's
   switch(n)
     {
      case 2:
         for(opidx=0; opidx<operandscnt; opidx++)
           {
            aoffset=offs+opidx*operandsize*2;
            a0x=a[aoffset];
            a0y=a[aoffset+1];
            a1x=a[aoffset+2];
            a1y=a[aoffset+3];
            a.Set(aoffset,a0x+a1x);
            a.Set(aoffset+1,a0y+a1y);
            a.Set(aoffset+2,a0x-a1x);
            a.Set(aoffset+3,a0y-a1y);
           }
         break;
      case 3:
         c1=MathCos(2*M_PI/3.0)-1;
         c2=MathSin(2*M_PI/3.0);
         for(opidx=0; opidx<operandscnt; opidx++)
           {
            aoffset=offs+opidx*operandsize*2;
            a0x=a[aoffset+0];
            a0y=a[aoffset+1];
            a1x=a[aoffset+2];
            a1y=a[aoffset+3];
            a2x=a[aoffset+4];
            a2y=a[aoffset+5];
            t1x=a1x+a2x;
            t1y=a1y+a2y;
            a0x+=t1x;
            a0y+=t1y;
            m1x=c1*t1x;
            m1y=c1*t1y;
            m2x=c2*(a1y-a2y);
            m2y=c2*(a2x-a1x);
            s1x=a0x+m1x;
            s1y=a0y+m1y;
            a1x=s1x+m2x;
            a1y=s1y+m2y;
            a2x=s1x-m2x;
            a2y=s1y-m2y;
            a.Set(aoffset,a0x);
            a.Set(aoffset+1,a0y);
            a.Set(aoffset+2,a1x);
            a.Set(aoffset+3,a1y);
            a.Set(aoffset+4,a2x);
            a.Set(aoffset+5,a2y);
           }
         break;
      case 4:
         for(opidx=0; opidx<operandscnt; opidx++)
           {
            aoffset=offs+opidx*operandsize*2;
            a0x=a[aoffset+0];
            a0y=a[aoffset+1];
            a1x=a[aoffset+2];
            a1y=a[aoffset+3];
            a2x=a[aoffset+4];
            a2y=a[aoffset+5];
            a3x=a[aoffset+6];
            a3y=a[aoffset+7];
            t1x=a0x+a2x;
            t1y=a0y+a2y;
            t2x=a1x+a3x;
            t2y=a1y+a3y;
            m2x=a0x-a2x;
            m2y=a0y-a2y;
            m3x=a1y-a3y;
            m3y=a3x-a1x;
            a.Set(aoffset,t1x+t2x);
            a.Set(aoffset+1,t1y+t2y);
            a.Set(aoffset+4,t1x-t2x);
            a.Set(aoffset+5,t1y-t2y);
            a.Set(aoffset+2,m2x+m3x);
            a.Set(aoffset+3,m2y+m3y);
            a.Set(aoffset+6,m2x-m3x);
            a.Set(aoffset+7,m2y-m3y);
           }
         break;
      case 5:
         v=2*M_PI/5.0;
         c1=(MathCos(v)+MathCos(2*v))/2.0-1;
         c2=(MathCos(v)-MathCos(2*v))/2.0;
         c3=-MathSin(v);
         c4=-(MathSin(v)+MathSin(2*v));
         c5=MathSin(v)-MathSin(2*v);
         for(opidx=0; opidx<operandscnt; opidx++)
           {
            aoffset=offs+opidx*operandsize*2;
            t1x=a[aoffset+2]+a[aoffset+8];
            t1y=a[aoffset+3]+a[aoffset+9];
            t2x=a[aoffset+4]+a[aoffset+6];
            t2y=a[aoffset+5]+a[aoffset+7];
            t3x=a[aoffset+2]-a[aoffset+8];
            t3y=a[aoffset+3]-a[aoffset+9];
            t4x=a[aoffset+6]-a[aoffset+4];
            t4y=a[aoffset+7]-a[aoffset+5];
            t5x=t1x+t2x;
            t5y=t1y+t2y;
            a.Set(aoffset,a[aoffset]+t5x);
            a.Set(aoffset+1,a[aoffset+1]+t5y);
            m1x=c1*t5x;
            m1y=c1*t5y;
            m2x=c2*(t1x-t2x);
            m2y=c2*(t1y-t2y);
            m3x=-(c3*(t3y+t4y));
            m3y=c3*(t3x+t4x);
            m4x=-(c4*t4y);
            m4y=c4*t4x;
            m5x=-(c5*t3y);
            m5y=c5*t3x;
            s3x=m3x-m4x;
            s3y=m3y-m4y;
            s5x=m3x+m5x;
            s5y=m3y+m5y;
            s1x=a[aoffset+0]+m1x;
            s1y=a[aoffset+1]+m1y;
            s2x=s1x+m2x;
            s2y=s1y+m2y;
            s4x=s1x-m2x;
            s4y=s1y-m2y;
            a.Set(aoffset+2,s2x+s3x);
            a.Set(aoffset+3,s2y+s3y);
            a.Set(aoffset+4,s4x+s5x);
            a.Set(aoffset+5,s4y+s5y);
            a.Set(aoffset+6,s4x-s5x);
            a.Set(aoffset+7,s4y-s5y);
            a.Set(aoffset+8,s2x-s3x);
            a.Set(aoffset+9,s2y-s3y);
           }
         break;
      case 6:
         c1=MathCos(2*M_PI/3.0)-1;
         c2=MathSin(2*M_PI/3.0);
         c3=MathCos(-(M_PI/3.0));
         c4=MathSin(-(M_PI/3.0));
         for(opidx=0; opidx<operandscnt; opidx++)
           {
            aoffset=offs+opidx*operandsize*2;
            a0x=a[aoffset+0];
            a0y=a[aoffset+1];
            a1x=a[aoffset+2];
            a1y=a[aoffset+3];
            a2x=a[aoffset+4];
            a2y=a[aoffset+5];
            a3x=a[aoffset+6];
            a3y=a[aoffset+7];
            a4x=a[aoffset+8];
            a4y=a[aoffset+9];
            a5x=a[aoffset+10];
            a5y=a[aoffset+11];
            v0=a0x;
            v1=a0y;
            a0x+=a3x;
            a0y+=a3y;
            a3x=v0-a3x;
            a3y=v1-a3y;
            v0=a1x;
            v1=a1y;
            a1x+=a4x;
            a1y+=a4y;
            a4x=v0-a4x;
            a4y=v1-a4y;
            v0=a2x;
            v1=a2y;
            a2x+=a5x;
            a2y+=a5y;
            a5x=v0-a5x;
            a5y=v1-a5y;
            t4x=a4x*c3-a4y*c4;
            t4y=a4x*c4+a4y*c3;
            a4x=t4x;
            a4y=t4y;
            t5x=-(a5x*c3)-a5y*c4;
            t5y=a5x*c4-a5y*c3;
            a5x=t5x;
            a5y=t5y;
            t1x=a1x+a2x;
            t1y=a1y+a2y;
            a0x=a0x+t1x;
            a0y=a0y+t1y;
            m1x=c1*t1x;
            m1y=c1*t1y;
            m2x=c2*(a1y-a2y);
            m2y=c2*(a2x-a1x);
            s1x=a0x+m1x;
            s1y=a0y+m1y;
            a1x=s1x+m2x;
            a1y=s1y+m2y;
            a2x=s1x-m2x;
            a2y=s1y-m2y;
            t1x=a4x+a5x;
            t1y=a4y+a5y;
            a3x=a3x+t1x;
            a3y=a3y+t1y;
            m1x=c1*t1x;
            m1y=c1*t1y;
            m2x=c2*(a4y-a5y);
            m2y=c2*(a5x-a4x);
            s1x=a3x+m1x;
            s1y=a3y+m1y;
            a4x=s1x+m2x;
            a4y=s1y+m2y;
            a5x=s1x-m2x;
            a5y=s1y-m2y;
            a.Set(aoffset,a0x);
            a.Set(aoffset+1,a0y);
            a.Set(aoffset+2,a3x);
            a.Set(aoffset+3,a3y);
            a.Set(aoffset+4,a1x);
            a.Set(aoffset+5,a1y);
            a.Set(aoffset+6,a4x);
            a.Set(aoffset+7,a4y);
            a.Set(aoffset+8,a2x);
            a.Set(aoffset+9,a2y);
            a.Set(aoffset+10,a5x);
            a.Set(aoffset+11,a5y);
           }
         break;
     }
  }
//+------------------------------------------------------------------+
//| This subroutine applies complex "integrated" codelet FFT  to     |
//| input/output array A. "Integrated" codelet differs from "normal" |
//| one in following ways:                                           |
//|   * it can work with MicrovectorSize > 1                         |
//|   * hence, it can be used in Cooley-Tukey FFT without            |
//|     transpositions                                               |
//|   * it performs inlined multiplication by twiddle factors of     |
//|     Cooley-Tukey FFT with N2=MicrovectorSize/2.                  |
//| INPUT PARAMETERS:                                                |
//|   A              -  array, must be large enough for plan to work |
//|   Offs           -  offset of the subarray to process            |
//|   OperandsCnt    -  operands count (see description of           |
//|                     FastTransformPlan)                           |
//|   OperandSize    -  operand size (see description of             |
//|                     FastTransformPlan)                           |
//|   MicrovectorSize-  microvector size, must be 1                  |
//| OUTPUT PARAMETERS:                                               |
//|   A              -  transformed array                            |
//+------------------------------------------------------------------+
void CFtBase::FtApplyComplexCodeLetTwFFT(CRowDouble &a,int offs,
                                         int operandscnt,
                                         int operandsize,
                                         int microvectorsize)
  {
//--- check
   if(!CAp::Assert(operandscnt>=1,__FUNCTION__": OperandsCnt<1"))
      return;
   if(!CAp::Assert(operandsize>=1,__FUNCTION__": OperandSize<1"))
      return;
   if(!CAp::Assert(microvectorsize>=1,__FUNCTION__": MicrovectorSize<>1"))
      return;
   if(!CAp::Assert(microvectorsize%2==0,__FUNCTION__": MicrovectorSize is not even"))
      return;
   if(!CAp::Assert(operandsize<=m_MaxRadix,__FUNCTION__": N>MaxRadix"))
      return;
//--- create variables
   int    opidx=0;
   int    mvidx=0;
   int    n=operandsize;
   int    m=microvectorsize/2;
   int    aoffset0=0;
   int    aoffset2=0;
   int    aoffset4=0;
   int    aoffset6=0;
   int    aoffset8=0;
   int    aoffset10=0;
   double a0x=0;
   double a0y=0;
   double a1x=0;
   double a1y=0;
   double a2x=0;
   double a2y=0;
   double a3x=0;
   double a3y=0;
   double a4x=0;
   double a4y=0;
   double a5x=0;
   double a5y=0;
   double v0=0;
   double v1=0;
   double v2=0;
   double v3=0;
   double q0x=0;
   double q0y=0;
   double t1x=0;
   double t1y=0;
   double t2x=0;
   double t2y=0;
   double t3x=0;
   double t3y=0;
   double t4x=0;
   double t4y=0;
   double t5x=0;
   double t5y=0;
   double m1x=0;
   double m1y=0;
   double m2x=0;
   double m2y=0;
   double m3x=0;
   double m3y=0;
   double m4x=0;
   double m4y=0;
   double m5x=0;
   double m5y=0;
   double s1x=0;
   double s1y=0;
   double s2x=0;
   double s2y=0;
   double s3x=0;
   double s3y=0;
   double s4x=0;
   double s4y=0;
   double s5x=0;
   double s5y=0;
   double c1=0;
   double c2=0;
   double c3=0;
   double c4=0;
   double c5=0;
   double v=0;
   double tw0=0;
   double tw1=0;
   double twx=0;
   double twxm1=0;
   double twy=0;
   double tw2x=0;
   double tw2y=0;
   double tw3x=0;
   double tw3y=0;
   double tw4x=0;
   double tw4y=0;
   double tw5x=0;
   double tw5y=0;
//--- Hard-coded transforms for different N's
   switch(n)
     {
      case 2:
         v=-(2*M_PI/(n*m));
         tw0=-(2*CMath::Sqr(MathSin(0.5*v)));
         tw1=MathSin(v);
         for(opidx=0; opidx<operandscnt; opidx++)
           {
            aoffset0=offs+opidx*operandsize*microvectorsize;
            aoffset2=aoffset0+microvectorsize;
            twxm1=0.0;
            twy=0.0;
            for(mvidx=0; mvidx<m; mvidx++)
              {
               a0x=a[aoffset0];
               a0y=a[aoffset0+1];
               a1x=a[aoffset2];
               a1y=a[aoffset2+1];
               v0=a0x+a1x;
               v1=a0y+a1y;
               v2=a0x-a1x;
               v3=a0y-a1y;
               a.Set(aoffset0,v0);
               a.Set(aoffset0+1,v1);
               a.Set(aoffset2,(v2*(1+twxm1)-v3*twy));
               a.Set(aoffset2+1,(v3*(1+twxm1)+v2*twy));
               aoffset0+=2;
               aoffset2+=2;
               if((mvidx+1)%m_updatetw==0)
                 {
                  v=-(2*M_PI*(mvidx+1)/(n*m));
                  twxm1=MathSin(0.5*v);
                  twxm1=-(2*twxm1*twxm1);
                  twy=MathSin(v);
                 }
               else
                 {
                  v=twxm1+tw0+twxm1*tw0-twy*tw1;
                  twy=twy+tw1+twxm1*tw1+twy*tw0;
                  twxm1=v;
                 }
              }
           }
         break;
      case 3:
         v=-(2*M_PI/(n*m));
         tw0=-(2*CMath::Sqr(MathSin(0.5*v)));
         tw1=MathSin(v);
         c1=MathCos(2*M_PI/3.0)-1;
         c2=MathSin(2*M_PI/3.0);
         for(opidx=0; opidx<operandscnt; opidx++)
           {
            aoffset0=offs+opidx*operandsize*microvectorsize;
            aoffset2=aoffset0+microvectorsize;
            aoffset4=aoffset2+microvectorsize;
            twx=1.0;
            twxm1=0.0;
            twy=0.0;
            for(mvidx=0; mvidx<m; mvidx++)
              {
               a0x=a[aoffset0];
               a0y=a[aoffset0+1];
               a1x=a[aoffset2];
               a1y=a[aoffset2+1];
               a2x=a[aoffset4];
               a2y=a[aoffset4+1];
               t1x=a1x+a2x;
               t1y=a1y+a2y;
               a0x=a0x+t1x;
               a0y=a0y+t1y;
               m1x=c1*t1x;
               m1y=c1*t1y;
               m2x=c2*(a1y-a2y);
               m2y=c2*(a2x-a1x);
               s1x=a0x+m1x;
               s1y=a0y+m1y;
               a1x=s1x+m2x;
               a1y=s1y+m2y;
               a2x=s1x-m2x;
               a2y=s1y-m2y;
               tw2x=twx*twx-twy*twy;
               tw2y=2*twx*twy;
               a.Set(aoffset0,a0x);
               a.Set(aoffset0+1,a0y);
               a.Set(aoffset2,(a1x*twx-a1y*twy));
               a.Set(aoffset2+1,(a1y*twx+a1x*twy));
               a.Set(aoffset4,(a2x*tw2x-a2y*tw2y));
               a.Set(aoffset4+1,(a2y*tw2x+a2x*tw2y));
               aoffset0=aoffset0+2;
               aoffset2=aoffset2+2;
               aoffset4=aoffset4+2;
               if((mvidx+1)%m_updatetw==0)
                 {
                  v=-(2*M_PI*(mvidx+1)/(n*m));
                  twxm1=MathSin(0.5*v);
                  twxm1=-(2*twxm1*twxm1);
                  twy=MathSin(v);
                  twx=twxm1+1;
                 }
               else
                 {
                  v=twxm1+tw0+twxm1*tw0-twy*tw1;
                  twy=twy+tw1+twxm1*tw1+twy*tw0;
                  twxm1=v;
                  twx=v+1;
                 }
              }
           }
         break;
      case 4:
         v=-(2*M_PI/(n*m));
         tw0=-(2*CMath::Sqr(MathSin(0.5*v)));
         tw1=MathSin(v);
         for(opidx=0; opidx<operandscnt; opidx++)
           {
            aoffset0=offs+opidx*operandsize*microvectorsize;
            aoffset2=aoffset0+microvectorsize;
            aoffset4=aoffset2+microvectorsize;
            aoffset6=aoffset4+microvectorsize;
            twx=1.0;
            twxm1=0.0;
            twy=0.0;
            for(mvidx=0; mvidx<m; mvidx++)
              {
               a0x=a[aoffset0];
               a0y=a[aoffset0+1];
               a1x=a[aoffset2];
               a1y=a[aoffset2+1];
               a2x=a[aoffset4];
               a2y=a[aoffset4+1];
               a3x=a[aoffset6];
               a3y=a[aoffset6+1];
               t1x=a0x+a2x;
               t1y=a0y+a2y;
               t2x=a1x+a3x;
               t2y=a1y+a3y;
               m2x=a0x-a2x;
               m2y=a0y-a2y;
               m3x=a1y-a3y;
               m3y=a3x-a1x;
               tw2x=twx*twx-twy*twy;
               tw2y=2*twx*twy;
               tw3x=twx*tw2x-twy*tw2y;
               tw3y=twx*tw2y+twy*tw2x;
               a1x=m2x+m3x;
               a1y=m2y+m3y;
               a2x=t1x-t2x;
               a2y=t1y-t2y;
               a3x=m2x-m3x;
               a3y=m2y-m3y;
               a.Set(aoffset0,t1x+t2x);
               a.Set(aoffset0+1,t1y+t2y);
               a.Set(aoffset2,(a1x*twx-a1y*twy));
               a.Set(aoffset2+1,(a1y*twx+a1x*twy));
               a.Set(aoffset4,(a2x*tw2x-a2y*tw2y));
               a.Set(aoffset4+1,(a2y*tw2x+a2x*tw2y));
               a.Set(aoffset6,(a3x*tw3x-a3y*tw3y));
               a.Set(aoffset6+1,(a3y*tw3x+a3x*tw3y));
               aoffset0=aoffset0+2;
               aoffset2=aoffset2+2;
               aoffset4=aoffset4+2;
               aoffset6=aoffset6+2;
               if((mvidx+1)%m_updatetw==0)
                 {
                  v=-(2*M_PI*(mvidx+1)/(n*m));
                  twxm1=MathSin(0.5*v);
                  twxm1=-(2*twxm1*twxm1);
                  twy=MathSin(v);
                  twx=twxm1+1;
                 }
               else
                 {
                  v=twxm1+tw0+twxm1*tw0-twy*tw1;
                  twy=twy+tw1+twxm1*tw1+twy*tw0;
                  twxm1=v;
                  twx=v+1;
                 }
              }
           }
         break;
      case 5:
         v=-(2*M_PI/(n*m));
         tw0=-(2*CMath::Sqr(MathSin(0.5*v)));
         tw1=MathSin(v);
         v=2*M_PI/5;
         c1=(MathCos(v)+MathCos(2*v))/2-1;
         c2=(MathCos(v)-MathCos(2*v))/2;
         c3=-MathSin(v);
         c4=-(MathSin(v)+MathSin(2*v));
         c5=MathSin(v)-MathSin(2*v);
         for(opidx=0; opidx<operandscnt; opidx++)
           {
            aoffset0=offs+opidx*operandsize*microvectorsize;
            aoffset2=aoffset0+microvectorsize;
            aoffset4=aoffset2+microvectorsize;
            aoffset6=aoffset4+microvectorsize;
            aoffset8=aoffset6+microvectorsize;
            twx=1.0;
            twxm1=0.0;
            twy=0.0;
            for(mvidx=0; mvidx<m; mvidx++)
              {
               a0x=a[aoffset0];
               a0y=a[aoffset0+1];
               a1x=a[aoffset2];
               a1y=a[aoffset2+1];
               a2x=a[aoffset4];
               a2y=a[aoffset4+1];
               a3x=a[aoffset6];
               a3y=a[aoffset6+1];
               a4x=a[aoffset8];
               a4y=a[aoffset8+1];
               t1x=a1x+a4x;
               t1y=a1y+a4y;
               t2x=a2x+a3x;
               t2y=a2y+a3y;
               t3x=a1x-a4x;
               t3y=a1y-a4y;
               t4x=a3x-a2x;
               t4y=a3y-a2y;
               t5x=t1x+t2x;
               t5y=t1y+t2y;
               q0x=a0x+t5x;
               q0y=a0y+t5y;
               m1x=c1*t5x;
               m1y=c1*t5y;
               m2x=c2*(t1x-t2x);
               m2y=c2*(t1y-t2y);
               m3x=-(c3*(t3y+t4y));
               m3y=c3*(t3x+t4x);
               m4x=-(c4*t4y);
               m4y=c4*t4x;
               m5x=-(c5*t3y);
               m5y=c5*t3x;
               s3x=m3x-m4x;
               s3y=m3y-m4y;
               s5x=m3x+m5x;
               s5y=m3y+m5y;
               s1x=q0x+m1x;
               s1y=q0y+m1y;
               s2x=s1x+m2x;
               s2y=s1y+m2y;
               s4x=s1x-m2x;
               s4y=s1y-m2y;
               tw2x=twx*twx-twy*twy;
               tw2y=2*twx*twy;
               tw3x=twx*tw2x-twy*tw2y;
               tw3y=twx*tw2y+twy*tw2x;
               tw4x=tw2x*tw2x-tw2y*tw2y;
               tw4y=tw2x*tw2y+tw2y*tw2x;
               a1x=s2x+s3x;
               a1y=s2y+s3y;
               a2x=s4x+s5x;
               a2y=s4y+s5y;
               a3x=s4x-s5x;
               a3y=s4y-s5y;
               a4x=s2x-s3x;
               a4y=s2y-s3y;
               a.Set(aoffset0,q0x);
               a.Set(aoffset0+1,q0y);
               a.Set(aoffset2,(a1x*twx-a1y*twy));
               a.Set(aoffset2+1,(a1x*twy+a1y*twx));
               a.Set(aoffset4,(a2x*tw2x-a2y*tw2y));
               a.Set(aoffset4+1,(a2x*tw2y+a2y*tw2x));
               a.Set(aoffset6,(a3x*tw3x-a3y*tw3y));
               a.Set(aoffset6+1,(a3x*tw3y+a3y*tw3x));
               a.Set(aoffset8,(a4x*tw4x-a4y*tw4y));
               a.Set(aoffset8+1,(a4x*tw4y+a4y*tw4x));
               aoffset0=aoffset0+2;
               aoffset2=aoffset2+2;
               aoffset4=aoffset4+2;
               aoffset6=aoffset6+2;
               aoffset8=aoffset8+2;
               if((mvidx+1)%m_updatetw==0)
                 {
                  v=-(2*M_PI*(mvidx+1)/(n*m));
                  twxm1=MathSin(0.5*v);
                  twxm1=-(2*twxm1*twxm1);
                  twy=MathSin(v);
                  twx=twxm1+1;
                 }
               else
                 {
                  v=twxm1+tw0+twxm1*tw0-twy*tw1;
                  twy=twy+tw1+twxm1*tw1+twy*tw0;
                  twxm1=v;
                  twx=v+1;
                 }
              }
           }
         break;
      case 6:
         c1=MathCos(2*M_PI/3)-1;
         c2=MathSin(2*M_PI/3);
         c3=MathCos(-(M_PI/3));
         c4=MathSin(-(M_PI/3));
         v=-(2*M_PI/(n*m));
         tw0=-(2*CMath::Sqr(MathSin(0.5*v)));
         tw1=MathSin(v);
         for(opidx=0; opidx<operandscnt; opidx++)
           {
            aoffset0=offs+opidx*operandsize*microvectorsize;
            aoffset2=aoffset0+microvectorsize;
            aoffset4=aoffset2+microvectorsize;
            aoffset6=aoffset4+microvectorsize;
            aoffset8=aoffset6+microvectorsize;
            aoffset10=aoffset8+microvectorsize;
            twx=1.0;
            twxm1=0.0;
            twy=0.0;
            for(mvidx=0; mvidx<m; mvidx++)
              {
               a0x=a[aoffset0+0];
               a0y=a[aoffset0+1];
               a1x=a[aoffset2+0];
               a1y=a[aoffset2+1];
               a2x=a[aoffset4+0];
               a2y=a[aoffset4+1];
               a3x=a[aoffset6+0];
               a3y=a[aoffset6+1];
               a4x=a[aoffset8+0];
               a4y=a[aoffset8+1];
               a5x=a[aoffset10+0];
               a5y=a[aoffset10+1];
               v0=a0x;
               v1=a0y;
               a0x=a0x+a3x;
               a0y=a0y+a3y;
               a3x=v0-a3x;
               a3y=v1-a3y;
               v0=a1x;
               v1=a1y;
               a1x=a1x+a4x;
               a1y=a1y+a4y;
               a4x=v0-a4x;
               a4y=v1-a4y;
               v0=a2x;
               v1=a2y;
               a2x=a2x+a5x;
               a2y=a2y+a5y;
               a5x=v0-a5x;
               a5y=v1-a5y;
               t4x=a4x*c3-a4y*c4;
               t4y=a4x*c4+a4y*c3;
               a4x=t4x;
               a4y=t4y;
               t5x=-(a5x*c3)-a5y*c4;
               t5y=a5x*c4-a5y*c3;
               a5x=t5x;
               a5y=t5y;
               t1x=a1x+a2x;
               t1y=a1y+a2y;
               a0x=a0x+t1x;
               a0y=a0y+t1y;
               m1x=c1*t1x;
               m1y=c1*t1y;
               m2x=c2*(a1y-a2y);
               m2y=c2*(a2x-a1x);
               s1x=a0x+m1x;
               s1y=a0y+m1y;
               a1x=s1x+m2x;
               a1y=s1y+m2y;
               a2x=s1x-m2x;
               a2y=s1y-m2y;
               t1x=a4x+a5x;
               t1y=a4y+a5y;
               a3x=a3x+t1x;
               a3y=a3y+t1y;
               m1x=c1*t1x;
               m1y=c1*t1y;
               m2x=c2*(a4y-a5y);
               m2y=c2*(a5x-a4x);
               s1x=a3x+m1x;
               s1y=a3y+m1y;
               a4x=s1x+m2x;
               a4y=s1y+m2y;
               a5x=s1x-m2x;
               a5y=s1y-m2y;
               tw2x=twx*twx-twy*twy;
               tw2y=2*twx*twy;
               tw3x=twx*tw2x-twy*tw2y;
               tw3y=twx*tw2y+twy*tw2x;
               tw4x=tw2x*tw2x-tw2y*tw2y;
               tw4y=2*tw2x*tw2y;
               tw5x=tw3x*tw2x-tw3y*tw2y;
               tw5y=tw3x*tw2y+tw3y*tw2x;
               a.Set(aoffset0,a0x);
               a.Set(aoffset0+1,a0y);
               a.Set(aoffset2,(a3x*twx-a3y*twy));
               a.Set(aoffset2+1,(a3y*twx+a3x*twy));
               a.Set(aoffset4,(a1x*tw2x-a1y*tw2y));
               a.Set(aoffset4+1,(a1y*tw2x+a1x*tw2y));
               a.Set(aoffset6,(a4x*tw3x-a4y*tw3y));
               a.Set(aoffset6+1,(a4y*tw3x+a4x*tw3y));
               a.Set(aoffset8,(a2x*tw4x-a2y*tw4y));
               a.Set(aoffset8+1,(a2y*tw4x+a2x*tw4y));
               a.Set(aoffset10,(a5x*tw5x-a5y*tw5y));
               a.Set(aoffset10+1,(a5y*tw5x+a5x*tw5y));
               aoffset0=aoffset0+2;
               aoffset2=aoffset2+2;
               aoffset4=aoffset4+2;
               aoffset6=aoffset6+2;
               aoffset8=aoffset8+2;
               aoffset10=aoffset10+2;
               if((mvidx+1)%m_updatetw==0)
                 {
                  v=-(2*M_PI*(mvidx+1)/(n*m));
                  twxm1=MathSin(0.5*v);
                  twxm1=-(2*twxm1*twxm1);
                  twy=MathSin(v);
                  twx=twxm1+1;
                 }
               else
                 {
                  v=twxm1+tw0+twxm1*tw0-twy*tw1;
                  twy=twy+tw1+twxm1*tw1+twy*tw0;
                  twxm1=v;
                  twx=v+1;
                 }
              }
           }
         break;
     }
  }
//+------------------------------------------------------------------+
//| This subroutine precomputes data for complex Bluestein's  FFT    |
//| and writes them to array PrecR[] at specified offset. It  is     |
//| responsibility of the caller to make sure that PrecR[] is large  |
//| enough.                                                          |
//| INPUT PARAMETERS:                                                |
//|   N        -  original size of the transform                     |
//|   M       - size of the "padded" Bluestein's transform         |
//|   PrecR    -  preallocated array                                 |
//|   Offs     -  offset                                             |
//| OUTPUT PARAMETERS:                                               |
//|   PrecR    -  data at Offs:Offs+4*M-1 are modified:              |
//|            * PrecR[Offs:Offs+2*M-1] stores Z[k]=exp(i*pi*k^2/N)  |
//|            * PrecR[Offs+2*M:Offs+4*M-1] stores FFT of the Z      |
//|              Other parts of PrecR are unchanged.                 |
//| NOTE: this function performs internal M-point FFT. It allocates  |
//|       temporary plan which is destroyed after leaving this       |
//|       function.                                                  |
//+------------------------------------------------------------------+
void CFtBase::FtPrecomputeBluesteinsFFT(int n,int m,CRowDouble &precr,
                                        int offs)
  {
//--- create variables
   int     i=0;
   double  bx=0;
   double  by=0;
   CFtPlan plan;
//--- Fill first half of PrecR with b[k] = exp(i*pi*k^2/N)
   if(offs==0 && (int)precr.Size()<=2*m)
      precr=vector<double>::Zeros(2*m);
   else
      for(i=0; i<2*m; i++)
         precr.Set(offs+i,0);
   for(i=0; i<n; i++)
     {
      bx=MathCos(M_PI/n*i*i);
      by=MathSin(M_PI/n*i*i);
      precr.Set(offs+2*i,bx);
      precr.Set(offs+2*i+1,by);
      precr.Set(offs+2*((m-i)%m),bx);
      precr.Set(offs+2*((m-i)%m)+1,by);
     }
//--- Precomputed FFT
   FtComplexFFTPlan(m,1,plan);
   for(i=0; i<2*m; i++)
      precr.Set(offs+2*m+i,precr[offs+i]);
   FtApplySubPlan(plan,0,precr,offs+2*m,0,plan.m_buffer,1);
  }
//+------------------------------------------------------------------+
//| This subroutine applies complex Bluestein's FFT to input/output  |
//| array A.                                                         |
//| INPUT PARAMETERS:                                                |
//|   Plan           -  transformation plan                          |
//|   A              -  array, must be large enough for plan to work |
//|   ABase          -  base offset in array A, this value points to |
//|                     start of subarray whose length is equal to   |
//|                     length of the plan                           |
//|   AOffset        -  offset with respect to ABase,                |
//|                     0 <= AOffset < PlanLength.                   |
//|                     This is an offset within large               |
//|                     PlanLength-subarray of the chunk to process. |
//|   OperandsCnt    -  number of repeated operands (length N each)  |
//|   N              -  original data length (measured in complex    |
//|                     numbers)                                     |
//|   M              -  padded data length (measured in complex      |
//|                     numbers)                                     |
//|   PrecOffs       -  offset of the precomputed data for the plan  |
//|   SubPlan        -  position of the length-M FFT subplan which   |
//|                     is used by transformation                    |
//|   BufA           -  temporary buffer, at least 2*M elements      |
//|   BufB           -  temporary buffer, at least 2*M elements      |
//|   BufC           -  temporary buffer, at least 2*M elements      |
//|   BufD           -  temporary buffer, at least 2*M elements      |
//| OUTPUT PARAMETERS:                                               |
//|   A              -  transformed array                            |
//+------------------------------------------------------------------+
void CFtBase::FtBluesteinsFFT(CFtPlan &plan,CRowDouble &a,int abase,
                              int aoffset,int operandscnt,int n,
                              int m,int precoffs,int subplan,
                              CRowDouble &bufa,CRowDouble &bufb,
                              CRowDouble &bufc,CRowDouble &bufd)
  {
//--- create variables
   int    op=0;
   int    i=0;
   double x=0;
   double y=0;
   double bx=0;
   double by=0;
   double ax=0;
   double ay=0;
   double rx=0;
   double ry=0;
   int    p0=0;
   int    p1=0;
   int    p2=0;

   for(op=0; op<operandscnt; op++)
     {
      //--- Multiply A by conj(Z), store to buffer.
      //--- Pad A by zeros.
      //--- NOTE: Z[k]=exp(i*pi*k^2/N)
      p0=abase+aoffset+op*2*n;
      p1=precoffs;
      for(i=0; i<n; i++)
        {
         x=a[p0+0];
         y=a[p0+1];
         bx=plan.m_precr[p1+0];
         by=-plan.m_precr[p1+1];
         bufa.Set(2*i,(x*bx-y*by));
         bufa.Set(2*i+1,(x*by+y*bx));
         p0=p0+2;
         p1=p1+2;
        }
      for(i=2*n; i<2*m; i++)
         bufa.Set(i,0);
      //--- Perform convolution of A and Z (using precomputed
      //--- FFT of Z stored in Plan structure).
      FtApplySubPlan(plan,subplan,bufa,0,0,bufc,1);
      p0=0;
      p1=precoffs+2*m;
      for(i=0; i<m; i++)
        {
         ax=bufa[p0+0];
         ay=bufa[p0+1];
         bx=plan.m_precr[p1+0];
         by=plan.m_precr[p1+1];
         bufa.Set(p0,(ax*bx-ay*by));
         bufa.Set(p0+1,-(ax*by+ay*bx));
         p0=p0+2;
         p1=p1+2;
        }
      FtApplySubPlan(plan,subplan,bufa,0,0,bufc,1);
      //--- Post processing:
      //---     A:=conj(Z)*conj(A)/M
      //--- Here conj(A)/M corresponds to last stage of inverse DFT,
      //--- and conj(Z) comes from Bluestein's FFT algorithm.
      p0=precoffs;
      p1=0;
      p2=abase+aoffset+op*2*n;
      for(i=0; i<n; i++)
        {
         bx=plan.m_precr[p0+0];
         by=plan.m_precr[p0+1];
         rx=bufa[p1+0]/m;
         ry=-(bufa[p1+1]/m);
         a.Set(p2,(rx*bx-ry*-by));
         a.Set(p2+1,(rx*-by+ry*bx));
         p0=p0+2;
         p1=p1+2;
         p2=p2+2;
        }
     }
  }
//+------------------------------------------------------------------+
//| This subroutine precomputes data for complex Rader's FFT and     |
//| writes them to array PrecR[] at specified offset. It  is         |
//| responsibility of the caller to make sure that PrecR[] is large  |
//| enough.                                                          |
//| INPUT PARAMETERS:                                                |
//|   N           -  original size of the transform (before reduction|
//|                  to N-1)                                         |
//|   RQ          -  primitive root modulo N                         |
//|   RIQ         -  inverse of primitive root modulo N              |
//|   PrecR       -  preallocated array                              |
//|   Offs        -  offset                                          |
//| OUTPUT PARAMETERS:                                               |
//|   PrecR       -  data at Offs:Offs+2*(N-1)-1 store FFT of Rader's|
//|                  factors, other parts of PrecR are unchanged.    |
//| NOTE: this function performs internal (N-1)-point FFT. It        |
//|       allocates temporary plan which is destroyed after leaving  |
//|       this function.                                             |
//+------------------------------------------------------------------+
void CFtBase::FtPrecomputeRadersFFT(int n,int rq,int riq,CRowDouble &precr,
                                    int offs)
  {
//--- create variables
   int     q=0;
   CFtPlan plan;
   int     kiq=0;
   double  v=0;
//-- Fill PrecR with Rader factors, perform FFT
   kiq=1;
   for(q=0; q<n-1; q++)
     {
      v=-(2*M_PI*kiq/n);
      precr.Set(offs+2*q,MathCos(v));
      precr.Set(offs+2*q+1,MathSin(v));
      kiq=kiq*riq%n;
     }
   FtComplexFFTPlan(n-1,1,plan);
   FtApplySubPlan(plan,0,precr,offs,0,plan.m_buffer,1);
  }
//+------------------------------------------------------------------+
//| This subroutine applies complex Rader's FFT to input/output      |
//| array A.                                                         |
//| INPUT PARAMETERS:                                                |
//|   A           -  array, must be large enough for plan to work    |
//|   ABase       -  base offset in array A, this value points to    |
//|                  start of subarray whose length is equal to      |
//|                  length of the plan                              |
//|   AOffset     -  offset with respect to ABase,                   |
//|                  0 <= AOffset < PlanLength.                      |
//|                  This is an offset within large                  |
//|                  PlanLength-subarray of the chunk to process.    |
//|   OperandsCnt -  number of repeated operands (length N each)     |
//|   N           -  original data length (measured in complex       |
//|                  numbers)                                        |
//|   SubPlan     -  position of the (N-1)-point FFT subplan which   |
//|                  is used by transformation                       |
//|   RQ          -  primitive root modulo N                         |
//|   RIQ         -  inverse of primitive root modulo N              |
//|   PrecOffs    -  offset of the precomputed data for the plan     |
//|   Buf         -  temporary array                                 |
//| OUTPUT PARAMETERS:                                               |
//|   A           -  transformed array                               |
//+------------------------------------------------------------------+
void CFtBase::FtRadersFFT(CFtPlan &plan,CRowDouble &a,int abase,
                          int aoffset,int operandscnt,int n,
                          int subplan,int rq,int riq,int precoffs,
                          CRowDouble &buf)
  {
//--- create variables
   int    opidx=0;
   int    i=0;
   int    q=0;
   int    kq=0;
   int    kiq=0;
   double x0=0;
   double y0=0;
   int    p0=0;
   int    p1=0;
   double ax=0;
   double ay=0;
   double bx=0;
   double by=0;
   double rx=0;
   double ry=0;
//--- check
   if(!CAp::Assert(operandscnt>=1,__FUNCTION__": OperandsCnt<1"))
      return;
//--- Process operands
   for(opidx=0; opidx<operandscnt; opidx++)
     {
      //--- fill QA
      kq=1;
      p0=abase+aoffset+opidx*n*2;
      p1=aoffset+opidx*n*2;
      rx=a[p0+0];
      ry=a[p0+1];
      x0=rx;
      y0=ry;
      for(q=0; q<n-1; q++)
        {
         ax=a[p0+2*kq];
         ay=a[p0+2*kq+1];
         buf.Set(p1,ax);
         buf.Set(p1+1,ay);
         rx=rx+ax;
         ry=ry+ay;
         kq=kq*rq%n;
         p1=p1+2;
        }
      p0=abase+aoffset+opidx*n*2;
      p1=aoffset+opidx*n*2;
      for(q=0; q<n-1; q++)
        {
         a.Set(p0,buf[p1]);
         a.Set(p0+1,buf[p1+1]);
         p0+=2;
         p1+=2;
        }
      //--- Convolution
      FtApplySubPlan(plan,subplan,a,abase,aoffset+opidx*n*2,buf,1);
      p0=abase+aoffset+opidx*n*2;
      p1=precoffs;
      for(i=0; i<n-1; i++)
        {
         ax=a[p0+0];
         ay=a[p0+1];
         bx=plan.m_precr[p1+0];
         by=plan.m_precr[p1+1];
         a.Set(p0+0,(ax*bx-ay*by));
         a.Set(p0+1,-(ax*by+ay*bx));
         p0+=2;
         p1+=2;
        }
      FtApplySubPlan(plan,subplan,a,abase,aoffset+opidx*n*2,buf,1);
      p0=abase+aoffset+opidx*n*2;
      for(i=0; i<n-1; i++)
        {
         a.Set(p0,a[p0]/(n-1));
         a.Set(p0+1,-(a[p0+1]/(n-1)));
         p0=p0+2;
        }
      //--- Result
      buf.Set(aoffset+opidx*n*2,rx);
      buf.Set(aoffset+opidx*n*2+1,ry);
      kiq=1;
      p0=aoffset+opidx*n*2;
      p1=abase+aoffset+opidx*n*2;
      for(q=0; q<n-1; q++)
        {
         buf.Set(p0+2*kiq+0,(x0+a[p1+0]));
         buf.Set(p0+2*kiq+1,(y0+a[p1+1]));
         kiq=kiq*riq%n;
         p1=p1+2;
        }
      p0=abase+aoffset+opidx*n*2;
      p1=aoffset+opidx*n*2;
      for(q=0; q<n; q++)
        {
         a.Set(p0,buf[p1]);
         a.Set(p0+1,buf[p1+1]);
         p0+=2;
         p1+=2;
        }
     }
  }
//+------------------------------------------------------------------+
//| Factorizes task size N into product of two smaller sizes N1      |
//| and N2                                                           |
//| INPUT PARAMETERS:                                                |
//|   N        -  task size, N>0                                     |
//|   IsRoot   -  whether taks is root task (first one in a sequence)|
//| OUTPUT PARAMETERS:                                               |
//|   N1, N2   -  such numbers that:                                 |
//|               * for prime N:                  N1=N2=0            |
//|               * for composite N<=MaxRadix:    N1=N2=0            |
//|               * for composite N>MaxRadix:     1<=N1<=N2, N1*N2=N |
//+------------------------------------------------------------------+
void CFtBase::FtFactorize(int n,bool IsRoot,int &n1,int &n2)
  {
//---check
   if(!CAp::Assert(n>0,__FUNCTION__": N<=0"))
      return;
//--- create variables
   int j=0;
   int k=0;
   n1=0;
   n2=0;
//--- Small N
   if(n<=m_MaxRadix)
      return;
//--- Large N, recursive split
   if(n>m_recursivethreshold)
     {
      k=(int)MathCeil(MathSqrt(n))+1;
      if(!CAp::Assert(k*k>=n,__FUNCTION__": internal error during recursive factorization"))
         return;
      for(j=k; j>=2; j--)
        {
         if(n%j==0)
           {
            n1=MathMin(n/j,j);
            n2=MathMax(n/j,j);
            return;
           }
        }
     }
//--- N > MaxRadix, try to find good codelet
   for(j=m_MaxRadix; j>=2; j--)
     {
      if(n%j==0)
        {
         n1=j;
         n2=n/j;
         break;
        }
     }
//--- In case no good codelet was found,
//--- try to factorize N into product of ANY primes.
   if(n1*n2!=n)
     {
      for(j=2; j<n; j++)
        {
         if(n%j==0)
           {
            n1=j;
            n2=n/j;
            break;
           }
         if(j*j>n)
            break;
        }
     }
//--- normalize
   if(n1>n2)
     {
      j=n1;
      n1=n2;
      n2=j;
     }
  }
//+------------------------------------------------------------------+
//| Returns optimistic estimate of the FFT cost, in UNITs            |
//| (1 UNIT = 100 KFLOPs)                                            |
//| INPUT PARAMETERS:                                                |
//|   N        -  task size, N>0                                     |
//| RESULT: cost in UNITs, rounded down to nearest integer           |
//| NOTE: If FFT cost is less than 1 UNIT, it will return 0 as result|
//+------------------------------------------------------------------+
int CFtBase::FtOptimisticEstimate(int n)
  {
//--- check
   if(!CAp::Assert(n>0,__FUNCTION__": N<=0"))
      return(0);

   int result=(int)MathFloor(1.0E-5*5*n*MathLog(n)/MathLog(2));
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| Twiddle factors calculation                                      |
//+------------------------------------------------------------------+
void CFtBase::FFtTwCalc(CRowDouble &a,const int aoffset,const int n1,
                        const int n2)
  {
//--- create variables
   int    i=0;
   int    j=0;
   int    n=0;
   int    halfn1=0;
   int    offs=0;
   double x=0;
   double y=0;
   double twxm1=0;
   double twy=0;
   double twbasexm1=0;
   double twbasey=0;
   double twrowxm1=0;
   double twrowy=0;
   double tmpx=0;
   double tmpy=0;
   double v=0;
   int    updatetw2=0;
//--- Multiplication by twiddle factors for complex Cooley-Tukey FFT
//--- with N factorized as N1*N2.
//--- Naive solution to this problem is given below:
//---     > for K:=1 to N2-1 do
//---     >     for J:=1 to N1-1 do
//---     >     begin
//---     >         Idx:=K*N1+J;
//---     >         X:=A[AOffset+2*Idx+0];
//---     >         Y:=A[AOffset+2*Idx+1];
//---     >         TwX:=Cos(-2*Pi()*K*J/(N1*N2));
//---     >         TwY:=Sin(-2*Pi()*K*J/(N1*N2));
//---     >         A[AOffset+2*Idx+0]:=X*TwX-Y*TwY;
//---     >         A[AOffset+2*Idx+1]:=X*TwY+Y*TwX;
//---     >     end;
//--- However, there are exist more efficient solutions.
//--- Each pass of the inner cycle corresponds to multiplication of one
//--- entry of A by W[k,j]=exp(-I*2*pi*k*j/N). This factor can be rewritten
//--- as exp(-I*2*pi*k/N)^j. So we can replace costly exponentiation by
//--- repeated multiplication: W[k,j+1]=W[k,j]*exp(-I*2*pi*k/N), with
//--- second factor being computed once in the beginning of the iteration.
//--- Also, exp(-I*2*pi*k/N) can be represented as exp(-I*2*pi/N)^k, i.e.
//--- we have W[K+1,1]=W[K,1]*W[1,1].
//--- In our loop we use following variables:
//--- * [TwBaseXM1,TwBaseY] =   [cos(2*pi/N)-1,     sin(2*pi/N)]
//--- * [TwRowXM1, TwRowY]  =   [cos(2*pi*I/N)-1,   sin(2*pi*I/N)]
//--- * [TwXM1,    TwY]     =   [cos(2*pi*I*J/N)-1, sin(2*pi*I*J/N)]
//--- Meaning of the variables:
//--- * [TwXM1,TwY] is current twiddle factor W[I,J]
//--- * [TwRowXM1, TwRowY] is W[I,1]
//--- * [TwBaseXM1,TwBaseY] is W[1,1]
//--- During inner loop we multiply current twiddle factor by W[I,1],
//--- during outer loop we update W[I,1].
   if(!CAp::Assert(m_updatetw>=2,__FUNCTION__": internal error - UpdateTw<2"))
      return;
   updatetw2=m_updatetw/2;
   halfn1=n1/2;
   n=n1*n2;
   v=-(2*M_PI/n);
   twbasexm1=-(2*CMath::Sqr(MathSin(0.5*v)));
   twbasey=MathSin(v);
   twrowxm1=0;
   twrowy=0;
   offs=aoffset;
//--- calculation
   for(i=0; i<n2; i++)
     {
      //--- Initialize twiddle factor for current row
      twxm1=0;
      twy=0;
      //--- N1-point block is separated into 2-point chunks and residual 1-point chunk
      //--- (in case N1 is odd). Unrolled loop is several times faster.
      for(j=0; j<halfn1; j++)
        {
         //--- Processing:
         //--- * process first element in a chunk.
         //--- * update twiddle factor (unconditional update)
         //--- * process second element
         //--- * conditional update of the twiddle factor
         x=a[offs];
         y=a[offs+1];
         tmpx=x*(1+twxm1)-y*twy;
         tmpy=x*twy+y*(1+twxm1);
         a.Set(offs,tmpx);
         a.Set(offs+1,tmpy);
         tmpx=(1+twxm1)*twrowxm1-twy*twrowy;
         twy+=(1+twxm1)*twrowy+twy*twrowxm1;
         twxm1+=tmpx;
         x=a[offs+2];
         y=a[offs+3];
         tmpx=x*(1+twxm1)-y*twy;
         tmpy=x*twy+y*(1+twxm1);
         a.Set(offs+2,tmpx);
         a.Set(offs+3,tmpy);
         offs+=4;
         if((j+1)%updatetw2==0 && j<halfn1-1)
           {
            //--- Recalculate twiddle factor
            v=-(2*M_PI*i*2*(j+1)/n);
            twxm1=MathSin(0.5*v);
            twxm1=-(2*twxm1*twxm1);
            twy=MathSin(v);
           }
         else
           {
            //--- Update twiddle factor
            tmpx=(1+twxm1)*twrowxm1-twy*twrowy;
            twy+=(1+twxm1)*twrowy+twy*twrowxm1;
            twxm1+=tmpx;
           }
        }
      if(n1%2==1)
        {
         //--- Handle residual chunk
         x=a[offs+0];
         y=a[offs+1];
         tmpx=x*(1+twxm1)-y*twy;
         tmpy=x*twy+y*(1+twxm1);
         a.Set(offs,tmpx);
         a.Set(offs+1,tmpy);
         offs+=2;
        }
      //--- update TwRow: TwRow(new) = TwRow(old)*TwBase
      if(i<n2-1)
        {
         if((i+1)%m_updatetw==0)
           {
            v=-(2*M_PI*(i+1)/n);
            twrowxm1=MathSin(0.5*v);
            twrowxm1=-(2*twrowxm1*twrowxm1);
            twrowy=MathSin(v);
           }
         else
           {
            tmpx=twbasexm1+twrowxm1*twbasexm1-twrowy*twbasey;
            tmpy=twbasey+twrowxm1*twbasey+twrowy*twbasexm1;
            twrowxm1+=tmpx;
            twrowy+=tmpy;
           }
        }
     }
  }
//+------------------------------------------------------------------+
//| Linear transpose: transpose complex matrix stored in             |
//| 1-dimensional array                                              |
//+------------------------------------------------------------------+
void CFtBase::InternalComplexLinTranspose(CRowDouble &a,const int m,
                                          const int n,const int astart,
                                          CRowDouble &buf)
  {
//--- function call
   FFtICLTRec(a,astart,n,buf,0,m,m,n);
   int i1_=-astart;
//--- calculation
   for(int i_=astart; i_<(astart+2*m*n); i_++)
      a.Set(i_,buf[i_+i1_]);
  }
//+------------------------------------------------------------------+
//| Linear transpose: transpose real matrix stored in 1-dimensional  |
//| array                                                            |
//+------------------------------------------------------------------+
void CFtBase::InternalRealLinTranspose(CRowDouble &a,const int m,
                                       const int n,const int astart,
                                       CRowDouble &buf)
  {
//--- function call
   FFtIRLTRec(a,astart,n,buf,0,m,m,n);
   int i1_=-astart;
//--- calculation
   for(int i_=astart; i_<(astart+m*n); i_++)
      a.Set(i_,buf[i_+i1_]);
  }
//+------------------------------------------------------------------+
//| Recurrent subroutine for a InternalComplexLinTranspose           |
//| Write A^T to B, where:                                           |
//| * A is m*n complex matrix stored in array A as pairs of          |
//|   real/image values, beginning from AStart position, with AStride|
//|   stride                                                         |
//| * B is n*m complex matrix stored in array B as pairs of          |
//|   real/image values, beginning from BStart position, with BStride|
//|   stride                                                         |
//| stride is measured in complex numbers, i.e. in real/image pairs. |
//+------------------------------------------------------------------+
void CFtBase::FFtICLTRec(CRowDouble &a,const int astart,const int astride,
                         CRowDouble &b,const int bstart,const int bstride,
                         const int m,const int n)
  {
//--- create variables
   int i=0;
   int j=0;
   int idx1=0;
   int idx2=0;
   int m2=0;
   int m1=0;
   int n1=0;
//--- check
   if(m==0 || n==0)
      return;
//--- check
   if(MathMax(m,n)<=8)
     {
      m2=2*bstride;
      for(i=0; i<m; i++)
        {
         //--- calculation
         idx1=bstart+2*i;
         idx2=astart+2*i*astride;
         for(j=0; j<n; j++)
           {
            b.Set(idx1,a[idx2+0]);
            b.Set(idx1+1,a[idx2+1]);
            idx1+=m2;
            idx2+=2;
           }
        }
      //--- exit the function
      return;
     }
//--- check
   if(n>m)
     {
      //--- New partition:
      //--- "A^T -> B" becomes "(A1 A2)^T -> ( B1 )
      //---                                  ( B2 )
      n1=n/2;
      //--- check
      if(n-n1>=8 && n1%8!=0)
         n1+=(8-n1%8);
      //--- check
      if(!CAp::Assert(n-n1>0))
         return;
      //--- function call
      FFtICLTRec(a,astart,astride,b,bstart,bstride,m,n1);
      //--- function call
      FFtICLTRec(a,astart+2*n1,astride,b,bstart+2*n1*bstride,bstride,m,n-n1);
     }
   else
     {
      //--- New partition:
      //--- "A^T -> B" becomes "( A1 )^T -> ( B1 B2 )
      //---                     ( A2 )
      m1=m/2;
      //--- check
      if(m-m1>=8 && m1%8!=0)
         m1+=(8-m1%8);
      //--- check
      if(!CAp::Assert(m-m1>0))
         return;
      //--- function call
      FFtICLTRec(a,astart,astride,b,bstart,bstride,m1,n);
      //--- function call
      FFtICLTRec(a,astart+2*m1*astride,astride,b,bstart+2*m1,bstride,m-m1,n);
     }
  }
//+------------------------------------------------------------------+
//| Recurrent subroutine for a InternalRealLinTranspose              |
//+------------------------------------------------------------------+
void CFtBase::FFtIRLTRec(CRowDouble &a,const int astart,const int astride,
                         CRowDouble &b,const int bstart,const int bstride,
                         const int m,const int n)
  {
//--- create variables
   int i=0;
   int j=0;
   int idx1=0;
   int idx2=0;
   int m1=0;
   int n1=0;
//--- check
   if(m==0 || n==0)
      return;
//--- check
   if(MathMax(m,n)<=8)
     {
      for(i=0; i<m; i++)
        {
         //--- calculation
         idx1=bstart+i;
         idx2=astart+i*astride;
         for(j=0; j<n; j++)
           {
            b.Set(idx1,a[idx2]);
            idx1=idx1+bstride;
            idx2=idx2+1;
           }
        }
      //--- exit the function
      return;
     }
//--- check
   if(n>m)
     {
      //--- New partition:
      //--- "A^T -> B" becomes "(A1 A2)^T -> ( B1 )
      //---                                  ( B2 )
      n1=n/2;
      //--- check
      if(n-n1>=8 && n1%8!=0)
         n1+=(8-n1%8);
      //--- check
      if(!CAp::Assert(n-n1>0))
         return;
      //--- function call
      FFtIRLTRec(a,astart,astride,b,bstart,bstride,m,n1);
      //--- function call
      FFtIRLTRec(a,astart+n1,astride,b,bstart+n1*bstride,bstride,m,n-n1);
     }
   else
     {
      //--- New partition:
      //--- "A^T -> B" becomes "( A1 )^T -> ( B1 B2 )
      //---                     ( A2 )
      m1=m/2;
      //--- check
      if(m-m1>=8 && m1%8!=0)
         m1+=(8-m1%8);
      //--- check
      if(!CAp::Assert(m-m1>0))
         return;
      //--- function call
      FFtIRLTRec(a,astart,astride,b,bstart,bstride,m1,n);
      //--- function call
      FFtIRLTRec(a,astart+m1*astride,astride,b,bstart+m1,bstride,m-m1,n);
     }
  }
//+------------------------------------------------------------------+
//| recurrent subroutine for FFTFindSmoothRec                        |
//+------------------------------------------------------------------+
void CFtBase::FtBaseFindSmoothRec(const int n,const int seed,
                                  const int leastfactor,int &best)
  {
//--- check
   if(!CAp::Assert(m_ftbasemaxsmoothfactor<=5,__FUNCTION__+": internal error!"))
      return;
//--- check
   if(seed>=n)
     {
      best=MathMin(best,seed);
      return;
     }
//--- check
   if(leastfactor<=2)
     {
      //--- function call
      FtBaseFindSmoothRec(n,seed*2,2,best);
     }
//--- check
   if(leastfactor<=3)
     {
      //--- function call
      FtBaseFindSmoothRec(n,seed*3,3,best);
     }
//--- check
   if(leastfactor<=5)
     {
      //--- function call
      FtBaseFindSmoothRec(n,seed*5,5,best);
     }
  }
//+------------------------------------------------------------------+
//| Auxiliary class for calculation mathematical functions           |
//+------------------------------------------------------------------+
class CNearUnitYUnit
  {
public:
   static double     NULog1p(const double x);
   static double     NUExp1m(const double x);
   static double     NUCos1m(const double x);
  };
//+------------------------------------------------------------------+
//| Log                                                              |
//+------------------------------------------------------------------+
double CNearUnitYUnit::NULog1p(const double x)
  {
//--- create variables
   double z=1.0+x;
   double lp=0;
   double lq=0;
//--- check
   if(z<0.70710678118654752440 || z>1.41421356237309504880)
      return(MathLog(z));
//--- calculation result
   z=x*x;
   lp=4.5270000862445199635215E-5;
   lp=lp*x+4.9854102823193375972212E-1;
   lp=lp*x+6.5787325942061044846969E0;
   lp=lp*x+2.9911919328553073277375E1;
   lp=lp*x+6.0949667980987787057556E1;
   lp=lp*x+5.7112963590585538103336E1;
   lp=lp*x+2.0039553499201281259648E1;
   lq=1.0000000000000000000000E0;
   lq=lq*x+1.5062909083469192043167E1;
   lq=lq*x+8.3047565967967209469434E1;
   lq=lq*x+2.2176239823732856465394E2;
   lq=lq*x+3.0909872225312059774938E2;
   lq=lq*x+2.1642788614495947685003E2;
   lq=lq*x+6.0118660497603843919306E1;
   z=-(0.5*z)+x*(z*lp/lq);
//--- return result
   return(x+z);
  }
//+------------------------------------------------------------------+
//| Exp                                                              |
//+------------------------------------------------------------------+
double CNearUnitYUnit::NUExp1m(const double x)
  {
//--- create variables
   double r;
   double xx;
   double ep;
   double eq;
//--- check
   if(x<-0.5 || x>0.5)
      return(MathExp(x)-1.0);
//--- calculation result
   xx=x*x;
   ep=1.2617719307481059087798E-4;
   ep=ep*xx+3.0299440770744196129956E-2;
   ep=ep*xx+9.9999999999999999991025E-1;
   eq=3.0019850513866445504159E-6;
   eq=eq*xx+2.5244834034968410419224E-3;
   eq=eq*xx+2.2726554820815502876593E-1;
   eq=eq*xx+2.0000000000000000000897E0;
   r=x*ep;
   r=r/(eq-r);
//--- return result
   return(r+r);
  }
//+------------------------------------------------------------------+
//| Cos                                                              |
//+------------------------------------------------------------------+
double CNearUnitYUnit::NUCos1m(const double x)
  {
//--- create variables
   double xx;
   double c;
//--- check
   if(x<-0.25*M_PI || x>0.25*M_PI)
      return(MathCos(x)-1);
//--- get result
   xx=x*x;
   c=4.7377507964246204691685E-14;
   c=c*xx-1.1470284843425359765671E-11;
   c=c*xx+2.0876754287081521758361E-9;
   c=c*xx-2.7557319214999787979814E-7;
   c=c*xx+2.4801587301570552304991E-5;
   c=c*xx-1.3888888888888872993737E-3;
   c=c*xx+4.1666666666666666609054E-2;
//--- return result
   return(-(0.5*xx)+xx*xx*c);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CNTheory
  {
public:
   static void       FindPrimitiveRootAndInverse(int n,int &proot,int &invproot);

private:
   static bool       IsPrime(int n);
   static int        ModMul(int a,int b,int n);
   static int        ModExp(int a,int b,int n);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CNTheory::FindPrimitiveRootAndInverse(int n,int &proot,int &invproot)
  {
//---check
   if(!CAp::Assert(n>=3,__FUNCTION__": N<3"))
      return;
//--- create variables
   int  candroot=0;
   int  phin=0;
   int  q=0;
   int  f=0;
   bool allnonone;
   int  x=0;
   int  lastx=0;
   int  y=0;
   int  lasty=0;
   int  a=0;
   int  b=0;
   int  t=0;
   int  n2=0;

   proot=0;
   invproot=0;
//--- check that N is prime
   if(!CAp::Assert(IsPrime(n),__FUNCTION__": N is not prime"))
      return;
//--- Because N is prime, Euler totient function is equal to N-1
   phin=n-1;
//--- Test different values of PRoot - from 2 to N-1.
//--- One of these values MUST be primitive root.
//--- For testing we use algorithm from Wiki (Primitive root modulo n):
//--- * compute phi(N)
//--- * determine the different prime factors of phi(N), say p1, ..., pk
//--- * for every element m of Zn*, compute m^(phi(N)/pi) mod N for i=1..k
//---   using a fast algorithm for modular exponentiation.
//--- * a number m for which these k results are all different from 1 is a
//---   primitive root.
   for(candroot=2; candroot<n; candroot++)
     {
      //--- We have current candidate root in CandRoot.
      //--- Scan different prime factors of PhiN. Here:
      //--- * F is a current candidate factor
      //--- * Q is a current quotient - amount which was left after dividing PhiN
      //---   by all previous factors
      //--- For each factor, perform test mentioned above.
      q=phin;
      f=2;
      allnonone=true;
      while(q>1)
        {
         if(q%f==0)
           {
            t=ModExp(candroot,phin/f,n);
            if(t==1)
              {
               allnonone=false;
               break;
              }
            while(q%f==0)
               q=q/f;
           }
         f++;
        }
      if(allnonone)
        {
         proot=candroot;
         break;
        }
     }
   if(!CAp::Assert(proot>=2,__FUNCTION__": internal error (root not found)"))
      return;
//--- Use extended Euclidean algorithm to find multiplicative inverse of primitive root
   x=0;
   lastx=1;
   y=1;
   lasty=0;
   a=proot;
   b=n;
   while(b!=0)
     {
      q=a/b;
      t=a%b;
      a=b;
      b=t;
      t=lastx-q*x;
      lastx=x;
      x=t;
      t=lasty-q*y;
      lasty=y;
      y=t;
     }
   while(lastx<0)
      lastx+=n;
   invproot=lastx;
//--- Check that it is safe to perform multiplication modulo N.
//--- Check results for consistency.
   n2=(n-1)*(n-1);
   if(!CAp::Assert(n2/(n-1)==n-1,__FUNCTION__": internal error"))
      return;
   if(!CAp::Assert(proot*invproot/proot==invproot,__FUNCTION__": internal error"))
      return;
   if(!CAp::Assert(proot*invproot/invproot==proot,__FUNCTION__": internal error"))
      return;
   if(!CAp::Assert(proot*invproot%n==1,__FUNCTION__": internal error"))
      return;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CNTheory::IsPrime(int n)
  {
   int p=2;

   while(p*p<=n)
     {
      if(n%p==0)
         return(false);
      p++;
     }
//--- return result
   return(true);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int CNTheory::ModMul(int a,int b,int n)
  {
//--- create variables
   int    result=0;
   int    t=0;
   double ra=(double)a;
   double rb=(double)b;
//--- check
   if(!CAp::Assert(a>=0 && a<n,__FUNCTION__": A<0 or A>=N"))
      return(result);
   if(!CAp::Assert(b>=0 && b<n,__FUNCTION__": B<0 or B>=N"))
      return(result);
//--- Base cases
   if(b==0 || a==0)
      return(result);

   if(b==1 || a==1)
      return(a*b);

   if((ra*rb)==(double)(a*b))
      return(a*b%n);
//--- Non-base cases
   if(b%2==0)
     {
      //--- A*B = (A*(B/2)) * 2
      //--- Product T=A*(B/2) is calculated recursively, product T*2 is
      //--- calculated as follows:
      //--- * result:=T-N
      //--- * result:=result+T
      //--- * if result<0 then result:=result+N
      //--- In case integer result overflows, we generate exception
      t=ModMul(a,b/2,n);
      result=2*t-n;
      if(result<0)
         result+=n;
     }
   else
     {
      //--- A*B = (A*(B div 2)) * 2 + A
      //--- Product T=A*(B/2) is calculated recursively, product T*2 is
      //--- calculated as follows:
      //--- * result:=T-N
      //--- * result:=result+T
      //--- * if result<0 then result:=result+N
      //--- In case integer result overflows, we generate exception
      t=ModMul(a,b/2,n);
      result=2*t-n;
      if(result<0)
         result=result+n;
      result=result-n;
      result=result+a;
      if(result<0)
         result=result+n;
     }
   return(result);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int CNTheory::ModExp(int a,int b,int n)
  {
//--- create variables
   int result=0;
   int t=0;
//--- check
   if(!CAp::Assert(a>=0 && a<n,__FUNCTION__": A<0 or A>=N"))
      return(result);
   if(!CAp::Assert(b>=0,__FUNCTION__": B<0"))
      return(result);
//--- Base cases
   if(b==0)
     {
      result=1;
      return(result);
     }
   if(b==1)
     {
      result=a;
      return(result);
     }
//--- Non-base cases
   if(b%2==0)
     {
      t=ModMul(a,a,n);
      result=ModExp(t,b/2,n);
     }
   else
     {
      t=ModMul(a,a,n);
      result=ModExp(t,b/2,n);
      result=ModMul(result,a,n);
     }
   return(result);
  }
//+------------------------------------------------------------------+
//| This structure stores temporary buffers used by gradient         |
//| calculation functions for neural networks.                       |
//+------------------------------------------------------------------+
struct CMLPBuffers
  {
   int               m_ChunkSize;
   int               m_NTotal;
   int               m_NIn;
   int               m_NOut;
   int               m_WCount;
   CRowDouble        m_Batch4Buf;
   CRowDouble        m_HPCBuf;
   CMatrixDouble     m_XY;
   CMatrixDouble     m_XY2;
   CRowDouble        m_XYRow;
   CRowDouble        m_X;
   CRowDouble        m_Y;
   CRowDouble        m_Desiredy;
   double            m_E;
   CRowDouble        m_G;
   CRowDouble        m_Tmp0;
   //--- constructor / destructor
                     CMLPBuffers(void) { m_ChunkSize=0; m_NTotal=0; m_NIn=0; m_NOut=0; m_WCount=0; m_E=0; }
                    ~CMLPBuffers(void) {}
   //---
   void              Copy(CMLPBuffers &obj);
   //--- overloading
   void              operator=(CMLPBuffers &obj) { Copy(obj); }
  };
//+------------------------------------------------------------------+
//| Copy                                                             |
//+------------------------------------------------------------------+
void CMLPBuffers::Copy(CMLPBuffers &obj)
  {
   m_ChunkSize=obj.m_ChunkSize;
   m_NTotal=obj.m_NTotal;
   m_NIn=obj.m_NIn;
   m_NOut=obj.m_NOut;
   m_WCount=obj.m_WCount;
   m_Batch4Buf=obj.m_Batch4Buf;
   m_HPCBuf=obj.m_HPCBuf;
   m_XY=obj.m_XY;
   m_XY2=obj.m_XY2;
   m_XYRow=obj.m_XYRow;
   m_X=obj.m_X;
   m_Y=obj.m_Y;
   m_Desiredy=obj.m_Desiredy;
   m_E=obj.m_E;
   m_G=obj.m_G;
   m_Tmp0=obj.m_Tmp0;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHPCCores
  {
public:
   static void       HPCPrepareChunkedGradient(CRowDouble &weights,int wcount,int NTotal,int NIn,int NOut,CMLPBuffers &buf);
   static void       HPCFinalizeChunkedGradient(CMLPBuffers &buf,CRowDouble &grad);
  };
//+------------------------------------------------------------------+
//| Prepares HPC compuations  of  chunked  gradient with             |
//| HPCChunkedGradient().                                            |
//| You  have to call this function  before  calling                 |
//| HPCChunkedGradient() for a new set of weights. You have to call  |
//| it only once, see example below:                                 |
//|   HOW TO PROCESS DATASET WITH THIS FUNCTION:                     |
//|      Grad:=0                                                     |
//|      HPCPrepareChunkedGradient(Weights,WCount,NTotal,NOut,Buf)   |
//|      foreach chunk-of-dataset do                                 |
//|         HPCChunkedGradient(...)                                  |
//|      HPCFinalizeChunkedGradient(Buf, Grad)                       |
//+------------------------------------------------------------------+
void CHPCCores::HPCPrepareChunkedGradient(CRowDouble &weights,
                                          int wcount,
                                          int NTotal,
                                          int NIn,
                                          int NOut,
                                          CMLPBuffers &buf)
  {
//--- create variables
   int ChunkSize=4;
   int batch4size=3*ChunkSize*NTotal+ChunkSize*(2*NOut+1);
//--- allocated
   if((int)buf.m_XY.Rows()<ChunkSize || (int)buf.m_XY.Cols()<(NIn+NOut))
      buf.m_XY.Resize(ChunkSize,NIn+NOut);
   if((int)buf.m_XY2.Rows()<ChunkSize || (int)buf.m_XY2.Cols()<(NIn+NOut))
      buf.m_XY2.Resize(ChunkSize,NIn+NOut);
   if((int)buf.m_XYRow.Size()<(NIn+NOut))
      buf.m_XYRow.Resize((ulong)(NIn+NOut));
   if((int)buf.m_X.Size()<NIn)
      buf.m_X.Resize(NIn);
   if((int)buf.m_Y.Size()<NOut)
      buf.m_Y.Resize(NOut);
   if((int)buf.m_Desiredy.Size()<NOut)
      buf.m_Desiredy.Resize(NOut);
   if((int)buf.m_Batch4Buf.Size()<batch4size)
      buf.m_Batch4Buf.Resize(batch4size);
   if((int)buf.m_G.Size()<wcount)
      buf.m_G.Resize(wcount);
   buf.m_HPCBuf=vector<double>::Zeros(wcount);
   buf.m_WCount=wcount;
   buf.m_NTotal=NTotal;
   buf.m_NIn=NIn;
   buf.m_NOut=NOut;
   buf.m_ChunkSize=ChunkSize;
  }
//+------------------------------------------------------------------+
//| Finalizes HPC compuations  of  chunked gradient with             |
//| HPCChunkedGradient().                                            |
//| You have to call this function after calling HPCChunkedGradient()|
//| for a new set of weights. You have to call it only once, see     |
//| example below:                                                   |
//|   HOW TO PROCESS DATASET WITH THIS FUNCTION:                     |
//|      Grad:=0                                                     |
//|      HPCPrepareChunkedGradient(Weights,WCount,NTotal,NOut,Buf)   |
//|      foreach chunk-of-dataset do                                 |
//|         HPCChunkedGradient(...)                                  |
//|   HPCFinalizeChunkedGradient(Buf, Grad)                          |
//+------------------------------------------------------------------+
void CHPCCores::HPCFinalizeChunkedGradient(CMLPBuffers &buf,CRowDouble &grad)
  {
   grad+=buf.m_HPCBuf;
  }
//+------------------------------------------------------------------+
