//+------------------------------------------------------------------+
//|                                                 dataanalysis.mqh |
//|            Copyright 2003-2022 Sergey Bochkanov (ALGLIB project) |
//|                             Copyright 2012-2026, MetaQuotes Ltd. |
//|                                                     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"
#include "optimization.mqh"
#include "statistics.mqh"
#include "solvers.mqh"
//+------------------------------------------------------------------+
//| Auxiliary class for CBdSS                                        |
//+------------------------------------------------------------------+
struct CCVReport
  {
   double            m_RelCLSError;
   double            m_AvgCE;
   double            m_RMSError;
   double            m_AvgError;
   double            m_AvgRelError;
   //---
                     CCVReport(void) { ZeroMemory(this); }
                    ~CCVReport(void) {}
   //---
   void              Copy(const CCVReport &obj);
   //--- overloading
   void              operator=(const CCVReport &obj) { Copy(obj); }
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CCVReport::Copy(const CCVReport &obj)
  {
   m_RelCLSError=obj.m_RelCLSError;
   m_AvgCE=obj.m_AvgCE;
   m_RMSError=obj.m_RMSError;
   m_AvgError=obj.m_AvgError;
   m_AvgRelError=obj.m_AvgRelError;
  }
//+------------------------------------------------------------------+
//| Data analysis                                                    |
//+------------------------------------------------------------------+
class CBdSS
  {
public:
   static void       DSErrAllocate(const int nclasses,double &buf[]);
   static void       DSErrAllocate(const int nclasses,CRowDouble &buf);
   static void       DSErrAccumulate(double &buf[],double &y[],double &desiredy[]);
   static void       DSErrAccumulate(CRowDouble &buf,CRowDouble &y,CRowDouble &desiredy);
   static void       DSErrFinish(double &buf[]);
   static void       DSErrFinish(CRowDouble &buf);
   static void       DSNormalize(CMatrixDouble &xy,const int npoints,const int nvars,int &info,double &means[],double &sigmas[]);
   static void       DSNormalize(CMatrixDouble &xy,const int npoints,const int nvars,int &info,CRowDouble &means,CRowDouble &sigmas);
   static void       DSNormalizeC(CMatrixDouble &xy,const int npoints,const int nvars,int &info,double &means[],double &sigmas[]);
   static void       DSNormalizeC(CMatrixDouble &xy,const int npoints,const int nvars,int &info,CRowDouble &means,CRowDouble &sigmas);
   static double     DSGetMeanMindIstance(CMatrixDouble &xy,const int npoints,const int nvars);
   static void       DSTie(double &a[],const int n,int &ties[],int &tiecount,int &p1[],int &p2[]);
   static void       DSTie(CRowDouble &a,const int n,CRowInt &ties,int &tiecount,CRowInt &p1,CRowInt &p2);
   static void       DSTieFastI(double &a[],int &b[],const int n,int &ties[],int &tiecount,double &bufr[],int &bufi[]);
   static void       DSTieFastI(CRowDouble &a,CRowInt &b,const int n,CRowInt &ties,int &tiecount,CRowDouble &bufr,CRowInt &bufi);
   static void       DSOptimalSplit2(double &ca[],int &cc[],const int n,int &info,double &threshold,double &pal,double &pbl,double &par,double &pbr,double &cve);
   static void       DSOptimalSplit2(CRowDouble &ca,CRowInt &cc,const int n,int &info,double &threshold,double &pal,double &pbl,double &par,double &pbr,double &cve);
   static void       DSOptimalSplit2Fast(double &a[],int &c[],int &tiesbuf[],int &cntbuf[],double &bufr[],int &bufi[],const int n,const int nc,double alpha,int &info,double &threshold,double &rms,double &cvrms);
   static void       DSOptimalSplit2Fast(CRowDouble &a,CRowInt &c,CRowInt &tiesbuf,CRowInt &cntbuf,CRowDouble &bufr,CRowInt &bufi,const int n,const int nc,double alpha,int &info,double &threshold,double &rms,double &cvrms);
   static void       DSSplitK(double &ca[],int &cc[],const int n,const int nc,int kmax,int &info,double &thresholds[],int &ni,double &cve);
   static void       DSSplitK(CRowDouble &ca,CRowInt &cc,const int n,const int nc,int kmax,int &info,CRowDouble &thresholds,int &ni,double &cve);
   static void       DSOptimalSplitK(double &ca[],int &cc[],const int n,const int nc,int kmax,int &info,double &thresholds[],int &ni,double &cve);
   static void       DSOptimalSplitK(CRowDouble &ca,CRowInt &cc,const int n,const int nc,int kmax,int &info,CRowDouble &thresholds,int &ni,double &cve);

private:
   static double     XLnY(const double x,const double y);
   static double     GetCV(CRowInt &cnt,const int nc);
   static void       TieAddC(CRowInt &c,CRowInt &ties,const int ntie,const int nc,CRowInt &cnt);
   static void       TieSubC(CRowInt &c,CRowInt &ties,const int ntie,const int nc,CRowInt &cnt);
  };
//+------------------------------------------------------------------+
//| This set of routines (DSErrAllocate, DSErrAccumulate,            |
//| DSErrFinish) calculates different error functions (classification|
//| error, cross-entropy, rms, avg, avg.rel errors).                 |
//| 1. DSErrAllocate prepares buffer.                                |
//| 2. DSErrAccumulate accumulates individual errors:                |
//|     * Y contains predicted output (posterior probabilities for   |
//|       classification)                                            |
//|     * DesiredY contains desired output (class number for         |
//|       classification)                                            |
//| 3. DSErrFinish outputs results:                                  |
//|    * Buf[0] contains relative classification error (zero for     |
//|      regression tasks)                                           |
//|    * Buf[1] contains avg. cross-entropy (zero for regression     |
//|      tasks)                                                      |
//|    * Buf[2] contains rms error (regression, classification)      |
//|    * Buf[3] contains average error (regression, classification)  |
//|    * Buf[4] contains average relative error (regression,         |
//|      classification)                                             |
//| NOTES(1):                                                        |
//|     "NClasses>0" means that we have classification task.         |
//|     "NClasses<0" means regression task with -NClasses real       |
//|     outputs.                                                     |
//| NOTES(2):                                                        |
//|     rms. avg, avg.rel errors for classification tasks are        |
//|     interpreted as errors in posterior probabilities with        |
//|     respect to probabilities given by training/test set.         |
//+------------------------------------------------------------------+
void CBdSS::DSErrAllocate(const int nclasses,double &buf[])
  {
   CRowDouble Buf;
   DSErrAllocate(nclasses,Buf);
   Buf.ToArray(buf);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CBdSS::DSErrAllocate(const int nclasses,CRowDouble &buf)
  {
//--- allocation
   buf=vector<double>::Zeros(8);
//--- initialization
   buf.Set(5,(double)nclasses);
  }
//+------------------------------------------------------------------+
//| See DSErrAllocate for comments on this routine.                  |
//+------------------------------------------------------------------+
void CBdSS::DSErrAccumulate(double &buf[],double &y[],double &desiredy[])
  {
   CRowDouble Buf=buf;
   CRowDouble Y=y;
   CRowDouble DesiredY=desiredy;
   DSErrAccumulate(Buf,Y,DesiredY);
   Buf.ToArray(buf);
  }
//+------------------------------------------------------------------+
//| See DSErrAllocate for comments on this routine.                  |
//+------------------------------------------------------------------+
void CBdSS::DSErrAccumulate(CRowDouble &buf,CRowDouble &y,CRowDouble &desiredy)
  {
//--- create variables
   int    offs=5;
   int    nclasses=(int)MathRound(buf[offs]);
   int    nout=0;
   int    mmax=0;
   int    rmax=0;
   int    j=0;
   double v=0;
//--- initialization
   vector<double> ev=vector<double>::Zeros(MathAbs(nclasses));
//--- check
   if(nclasses>0)
     {
      //--- Classification
      rmax=(int)MathRound(desiredy[0]);
      //--- initialization
      mmax=(int)y.ArgMax();
      //--- check
      if(mmax!=rmax)
         buf.Add(0,1);
      //--- check
      if(y[rmax]>0.0)
         buf.Add(1,-MathLog(y[rmax]));
      else
         buf.Add(1,MathLog(CMath::m_maxrealnumber));
      //--- calculation
      if(rmax>=0 && rmax<nclasses)
        {
         buf.Add(4,MathAbs(1-y[rmax]));
         buf.Add(offs+2,1);
         ev[rmax]=1;
        }
      vector<double> err=y.ToVector()-ev;
      buf.Add(2,MathPow(err,2.0).Sum());
      buf.Add(3,MathAbs(err).Sum());
      //--- change value
      buf.Add(offs+1,1);
     }
   else
     {
      //--- Regression
      nout=-nclasses;
      //--- initialization
      rmax=(int)desiredy.ArgMax();
      //--- initialization
      mmax=(int)y.ArgMax();
      //--- check
      if(mmax!=rmax)
         buf.Add(0,1);
      //--- calculation
      ev=desiredy.ToVector();
      vector<double> err=y.ToVector()-ev;
      buf.Add(2,MathPow(err,2.0).Sum());
      buf.Add(3,MathAbs(err).Sum());
      for(j=0; j<nout; j++)
        {
         //--- check
         if(ev[j]!=0.0)
           {
            buf.Add(4,MathAbs(err[j]/ev[j]));
            buf.Add(offs+2,1);
           }
        }
      //--- change value
      buf.Add(offs+1,1);
     }
  }
//+------------------------------------------------------------------+
//| See DSErrAllocate for comments on this routine.                  |
//+------------------------------------------------------------------+
void CBdSS::DSErrFinish(double &buf[])
  {
   CRowDouble Buf=buf;
   DSErrFinish(Buf);
   Buf.ToArray(buf);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CBdSS::DSErrFinish(CRowDouble &buf)
  {
//--- create variables
   int offs=5;
   int nout=MathAbs((int)MathRound(buf[offs]));
//--- check
   if(buf[offs+1]!=0.0)
     {
      //--- change values
      buf.Mul(0,1.0/buf[offs+1]);
      buf.Mul(1,1.0/buf[offs+1]);
      buf.Set(2,MathSqrt(buf[2]/(nout*buf[offs+1])));
      buf.Mul(3,1.0/(nout*buf[offs+1]));
     }
//--- check
   if(buf[offs+2]!=0.0)
      buf.Mul(4,1.0/buf[offs+2]);
  }
//+------------------------------------------------------------------+
//| Normalize                                                        |
//+------------------------------------------------------------------+
void CBdSS::DSNormalize(CMatrixDouble &xy,const int npoints,
                        const int nvars,int &info,double &means[],
                        double &sigmas[])
  {
   CRowDouble Means,Sigmas;
   DSNormalize(xy,npoints,nvars,info,Means,Sigmas);
   Means.ToArray(means);
   Sigmas.ToArray(sigmas);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CBdSS::DSNormalize(CMatrixDouble &xy,const int npoints,
                        const int nvars,int &info,CRowDouble &means,
                        CRowDouble &sigmas)
  {
//--- function call
   DSNormalizeC(xy,npoints,nvars,info,means,sigmas);
//--- calculation
   for(int j=0; j<nvars; j++)
     {
      //--- change values
      for(int i=0; i<npoints; i++)
         xy.Set(i,j,(xy.Get(i,j)-means[j])/sigmas[j]);
     }
  }
//+------------------------------------------------------------------+
//| Normalize                                                        |
//+------------------------------------------------------------------+
void CBdSS::DSNormalizeC(CMatrixDouble &xy,const int npoints,
                         const int nvars,int &info,double &means[],
                         double &sigmas[])
  {
   CRowDouble Means,Sigmas;
   DSNormalizeC(xy,npoints,nvars,info,Means,Sigmas);
   Means.ToArray(means);
   Sigmas.ToArray(sigmas);
  }
//+------------------------------------------------------------------+
//| Normalize                                                        |
//+------------------------------------------------------------------+
void CBdSS::DSNormalizeC(CMatrixDouble &xy,const int npoints,
                         const int nvars,int &info,CRowDouble &means,
                         CRowDouble &sigmas)
  {
//--- initialization
   info=0;
//--- Test parameters
   if(npoints<=0 || nvars<1)
     {
      info=-1;
      return;
     }
//--- copy
   matrix<double> tmp=xy.ToMatrix();
   tmp.Resize(npoints,nvars);
//--- change value
   info=1;
//--- Standartization
   means=tmp.Mean(0);
   sigmas=tmp.Std(0);
//--- calculation
   for(int j=0; j<nvars; j++)
      if(sigmas[j]==0.0)
         sigmas.Set(j,1);
  }
//+------------------------------------------------------------------+
//| Method                                                           |
//+------------------------------------------------------------------+
double CBdSS::DSGetMeanMindIstance(CMatrixDouble &xy,const int npoints,
                                   const int nvars)
  {
//--- create variables
   double result=0;
   double v=0;
//--- creating arrays
   vector<double> tmp;
   vector<double> tmp2;
//--- Test parameters
   if(npoints<=0 || nvars<1)
      return(0);
//--- Process
   tmp=vector<double>::Full(npoints,CMath::m_maxrealnumber);
//--- allocation
   for(int i=0; i<npoints; i++)
     {
      for(int j=i+1; j<npoints; j++)
        {
         //--- calculation
         tmp2=xy[i];
         tmp2.Resize(nvars);
         for(int i_=0; i_<nvars; i_++)
            tmp2[i_]-=xy.Get(j,i_);
         v=MathSqrt(MathPow(tmp2,2).Sum());
         //--- change values
         tmp[i]=MathMin(tmp[i],v);
         tmp[j]=MathMin(tmp[j],v);
        }
     }
//--- get result
   result=tmp.Sum()/npoints;
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| Method                                                           |
//+------------------------------------------------------------------+
void CBdSS::DSTie(double &a[],const int n,int &ties[],int &tiecount,
                  int &p1[],int &p2[])
  {
   CRowDouble A=a;
   CRowInt Ties=ties;
   CRowInt P1=p1;
   CRowInt P2=p2;
   DSTie(A,n,Ties,tiecount,P1,P2);
   A.ToArray(a);
   Ties.ToArray(ties);
   P1.ToArray(p1);
   P2.ToArray(p2);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CBdSS::DSTie(CRowDouble &a,const int n,CRowInt &ties,int &tiecount,
                  CRowInt &p1,CRowInt &p2)
  {
//--- initialization
   tiecount=0;
//--- Special case
   if(n<=0)
     {
      tiecount=0;
      return;
     }
//--- Sort A
   CTSort::TagSort(a,n,p1,p2);
//--- Process ties
//--- allocation
   ties.Resize(n+1);
//--- change values
   ties.Set(0,0);
   tiecount=1;
   for(int i=1; i<n; i++)
     {
      //--- check
      if(a[i]!=a[i-1])
        {
         ties.Set(tiecount,i);
         tiecount++;
        }
     }
//--- change value
   ties.Set(tiecount,n);
   ties.Resize(tiecount+1);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CBdSS::DSTieFastI(double &a[],int &b[],const int n,int &ties[],
                       int &tiecount,double &bufr[],int &bufi[])
  {
   CRowDouble A=a;
   CRowInt B=b;
   CRowInt Ties=ties;
   CRowDouble BufR=bufr;
   CRowInt BufI=bufi;
   DSTieFastI(A,B,n,Ties,tiecount,BufR,BufI);
   A.ToArray(a);
   B.ToArray(b);
   Ties.ToArray(ties);
   BufR.ToArray(bufr);
   BufI.ToArray(bufi);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CBdSS::DSTieFastI(CRowDouble &a,CRowInt &b,const int n,CRowInt &ties,
                       int &tiecount,CRowDouble &bufr,CRowInt &bufi)
  {
//--- Special case
   if(n<=0)
     {
      tiecount=0;
      return;
     }
//--- Sort A
   CTSort::TagSortFastI(a,b,bufr,bufi,n);
//--- Process ties
   ties.Set(0,0);
   int k=1;
//--- calculation
   for(int i=1; i<n; i++)
     {
      //--- check
      if(a[i]!=a[i-1])
        {
         ties.Set(k,i);
         k++;
        }
     }
//--- change values
   ties.Set(k,n);
   tiecount=k;
  }
//+------------------------------------------------------------------+
//| Optimal binary classification                                    |
//| Algorithms finds optimal (=with minimal cross-entropy) binary    |
//| partition.                                                       |
//| Internal subroutine.                                             |
//| INPUT PARAMETERS:                                                |
//|     A       -   array[0..N-1], variable                          |
//|     C       -   array[0..N-1], class numbers (0 or 1).           |
//|     N       -   array size                                       |
//| OUTPUT PARAMETERS:                                               |
//|     Info    -   completetion code:                               |
//|                 * -3, all values of A[] are same (partition is   |
//|                   impossible)                                    |
//|                 * -2, one of C[] is incorrect (<0, >1)           |
//|                 * -1, incorrect pararemets were passed (N<=0).   |
//|                 *  1, OK                                         |
//|     Threshold-  partiton boundary. Left part contains values     |
//|                 which are strictly less than Threshold. Right    |
//|                 part contains values which are greater than or   |
//|                 equal to Threshold.                              |
//|     PAL, PBL-   probabilities P(0|v<Threshold) and               |
//|                 P(1|v<Threshold)                                 |
//|     PAR, PBR-   probabilities P(0|v>=Threshold) and              |
//|                 P(1|v>=Threshold)                                |
//|     CVE     -   cross-validation estimate of cross-entropy       |
//+------------------------------------------------------------------+
void CBdSS::DSOptimalSplit2(double &ca[],int &cc[],const int n,
                            int &info,double &threshold,double &pal,
                            double &pbl,double &par,double &pbr,
                            double &cve)
  {
   CRowDouble A=ca;
   CRowInt C=cc;
   DSOptimalSplit2(A,C,n,info,threshold,pal,pbl,par,pbr,cve);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CBdSS::DSOptimalSplit2(CRowDouble &ca,CRowInt &cc,const int n,
                            int &info,double &threshold,double &pal,
                            double &pbl,double &par,double &pbr,
                            double &cve)
  {
//--- create variables
   int    i=0;
   int    t=0;
   double s=0;
   int    tiecount=0;
   int    k=0;
   int    koptimal=0;
   double pak=0;
   double pbk=0;
   double cvoptimal=0;
   double cv=0;
//--- creating arrays
   CRowInt    ties;
   CRowInt    p1;
   CRowInt    p2;
//--- copy
   CRowDouble a=ca;
   CRowInt    c=cc;
//--- initialization
   info=0;
   threshold=0;
   pal=0;
   pbl=0;
   par=0;
   pbr=0;
   cve=0;
//--- Test for errors in inputs
   if(n<=0)
     {
      info=-1;
      return;
     }
   for(i=0; i<n; i++)
     {
      //--- check
      if(c[i]!=0 && c[i]!=1)
        {
         info=-2;
         return;
        }
     }
//--- change value
   info=1;
//--- Tie
   DSTie(a,n,ties,tiecount,p1,p2);
//--- swap
   for(i=0; i<n; i++)
     {
      //--- check
      if(p2[i]!=i)
         c.Swap(i,p2[i]);
     }
//--- Special case: number of ties is 1.
//--- NOTE: we assume that P.Get(i,j) equals to 0 or 1,
//---       intermediate values are not allowed.
   if(tiecount==1)
     {
      info=-3;
      return;
     }
//--- General case,number of ties > 1
//--- NOTE: we assume that P.Get(i,j) equals to 0 or 1,
//---       intermediate values are not allowed.
   pal=0;
   pbl=0;
   par=0;
   pbr=0;
   for(i=0; i<n; i++)
     {
      //--- check
      if(c[i]==0)
         par=par+1;
      //--- check
      if(c[i]==1)
         pbr=pbr+1;
     }
//--- change values
   koptimal=-1;
   cvoptimal=CMath::m_maxrealnumber;
   for(k=0; k<tiecount-1; k++)
     {
      //--- first,obtain information about K-th tie which is
      //--- moved from R-part to L-part
      pak=0;
      pbk=0;
      for(i=ties[k]; i<ties[k+1]; i++)
        {
         //--- check
         if(c[i]==0)
            pak=pak+1;
         //--- check
         if(c[i]==1)
            pbk=pbk+1;
        }
      //--- Calculate cross-validation CE
      cv=0;
      cv=cv-XLnY(pal+pak,(pal+pak)/(pal+pak+pbl+pbk+1));
      cv=cv-XLnY(pbl+pbk,(pbl+pbk)/(pal+pak+1+pbl+pbk));
      cv=cv-XLnY(par-pak,(par-pak)/(par-pak+pbr-pbk+1));
      cv=cv-XLnY(pbr-pbk,(pbr-pbk)/(par-pak+1+pbr-pbk));
      //--- Compare with best
      if(cv<cvoptimal)
        {
         cvoptimal=cv;
         koptimal=k;
        }
      //--- update
      pal+=pak;
      pbl+=pbk;
      par-=pak;
      pbr-=pbk;
     }
//--- change values
   cve=cvoptimal;
   threshold=0.5*(a[ties[koptimal]]+a[ties[koptimal+1]]);
   pal=0;
   pbl=0;
   par=0;
   pbr=0;
   for(i=0; i<n; i++)
     {
      //--- check
      if(a[i]<threshold)
        {
         //--- check
         if(c[i]==0)
            pal=pal+1;
         else
            pbl=pbl+1;
        }
      else
        {
         //--- check
         if(c[i]==0)
            par=par+1;
         else
            pbr=pbr+1;
        }
     }
//--- change values
   s=pal+pbl;
   pal=pal/s;
   pbl=pbl/s;
   s=par+pbr;
   par=par/s;
   pbr=pbr/s;
  }
//+------------------------------------------------------------------+
//| Optimal partition, internal subroutine. Fast version.            |
//| Accepts:                                                         |
//|     A       array[0..N-1]       array of attributes array[0..N-1]|
//|     C       array[0..N-1]       array of class labels            |
//|     TiesBuf array[0..N]         temporaries (ties)               |
//|     CntBuf  array[0..2*NC-1]    temporaries (counts)             |
//|     Alpha                       centering factor (0<=alpha<=1,   |
//|                                 recommended value - 0.05)        |
//|     BufR    array[0..N-1]       temporaries                      |
//|     BufI    array[0..N-1]       temporaries                      |
//| Output:                                                          |
//|     Info    error code (">0"=OK, "<0"=bad)                       |
//|     RMS     training set RMS error                               |
//|     CVRMS   leave-one-out RMS error                              |
//| Note:                                                            |
//|     content of all arrays is changed by subroutine;              |
//|     it doesn't allocate temporaries.                             |
//+------------------------------------------------------------------+
void CBdSS::DSOptimalSplit2Fast(double &a[],int &c[],int &tiesbuf[],
                                int &cntbuf[],double &bufr[],int &bufi[],
                                const int n,const int nc,double alpha,
                                int &info,double &threshold,
                                double &rms,double &cvrms)
  {
   CRowDouble A=a;
   CRowInt C=c;
   CRowInt TiesBuf=tiesbuf;
   CRowInt CntBuf=cntbuf;
   CRowDouble BufR=bufr;
   CRowInt BufI=bufi;
   DSOptimalSplit2Fast(A,C,TiesBuf,CntBuf,BufR,BufI,n,nc,alpha,info,threshold,rms,cvrms);
   A.ToArray(a);
   C.ToArray(c);
   TiesBuf.ToArray(tiesbuf);
   CntBuf.ToArray(cntbuf);
   BufR.ToArray(bufr);
   BufI.ToArray(bufi);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CBdSS::DSOptimalSplit2Fast(CRowDouble &a,CRowInt &c,CRowInt &tiesbuf,
                                CRowInt &cntbuf,CRowDouble &bufr,CRowInt &bufi,
                                const int n,const int nc,double alpha,
                                int &info,double &threshold,
                                double &rms,double &cvrms)
  {
//--- create variables
   int    i=0;
   int    k=0;
   int    cl=0;
   int    tiecount=0;
   double cbest=0;
   double cc=0;
   int    koptimal=0;
   int    sl=0;
   int    sr=0;
   double v=0;
   double w=0;
   double x=0;
//--- initialization
   info=0;
   threshold=0;
   rms=0;
   cvrms=0;
//--- Test for errors in inputs
   if(n<=0 || nc<2)
     {
      info=-1;
      return;
     }
   for(i=0; i<n; i++)
     {
      //--- check
      if(c[i]<0 || c[i]>=nc)
        {
         info=-2;
         return;
        }
     }
//--- change value
   info=1;
//--- Tie
   DSTieFastI(a,c,n,tiesbuf,tiecount,bufr,bufi);
//--- Special case: number of ties is 1.
   if(tiecount==1)
     {
      info=-3;
      return;
     }
//--- General case,number of ties > 1
   cntbuf.Fill(0,0,2*nc);
   for(i=0; i<n; i++)
      cntbuf.Add(nc+c[i],1);
//--- change values
   koptimal=-1;
   threshold=a[n-1];
   cbest=CMath::m_maxrealnumber;
   sl=0;
   sr=n;
//--- calculation
   for(k=0; k<tiecount-1; k++)
     {
      //--- first,move Kth tie from right to left
      for(i=tiesbuf[k]; i<tiesbuf[k+1]; i++)
        {
         cl=c[i];
         cntbuf.Add(cl,1);
         cntbuf.Add(nc+cl,-1);
        }
      sl+=(tiesbuf[k+1]-tiesbuf[k]);
      sr-=(tiesbuf[k+1]-tiesbuf[k]);
      //--- Calculate RMS error
      v=0;
      for(i=0; i<nc; i++)
        {
         w=cntbuf[i];
         v+=w*CMath::Sqr(w/sl-1);
         v+=(sl-w)*CMath::Sqr(w/sl);
         w=cntbuf[nc+i];
         v+=w*CMath::Sqr(w/sr-1);
         v+=(sr-w)*CMath::Sqr(w/sr);
        }
      //--- change value
      v=MathSqrt(v/(nc*n));
      //--- Compare with best
      x=(2.0*sl)/(double)(sl+sr)-1.0;
      cc=v*(1.0+alpha*(CMath::Sqr(x)-1.0));
      //--- check
      if(cc<cbest)
        {
         //--- store split
         rms=v;
         koptimal=k;
         cbest=cc;
         //--- calculate CVRMS error
         cvrms=0;
         for(i=0; i<nc; i++)
           {
            //--- check
            if(sl>1)
              {
               w=cntbuf[i];
               cvrms+=w*CMath::Sqr((w-1.0)/(sl-1.0)-1.0);
               cvrms+=(sl-w)*CMath::Sqr(w/(sl-1.0));
              }
            else
              {
               w=cntbuf[i];
               cvrms+=w*CMath::Sqr(1.0/(double)nc-1.0);
               cvrms+=(sl-w)*CMath::Sqr(1.0/(double)nc);
              }
            //--- check
            if(sr>1)
              {
               w=cntbuf[nc+i];
               cvrms+=w*CMath::Sqr((w-1.0)/(sr-1.0)-1.0);
               cvrms+=(sr-w)*CMath::Sqr(w/(sr-1.0));
              }
            else
              {
               w=cntbuf[nc+i];
               cvrms+=w*CMath::Sqr(1.0/(double)nc-1.0);
               cvrms+=(sr-w)*CMath::Sqr(1.0/(double)nc);
              }
           }
         //--- change value
         cvrms=MathSqrt(cvrms/(double)(nc*n));
        }
     }
//--- Calculate threshold.
//--- Code is a bit complicated because there can be such
//--- numbers that 0.5(A+B) equals to A or B (if A-B=epsilon)
   threshold=0.5*(a[tiesbuf[koptimal]]+a[tiesbuf[koptimal+1]]);
//--- check
   if(threshold<=a[tiesbuf[koptimal]])
      threshold=a[tiesbuf[koptimal+1]];
  }
//+------------------------------------------------------------------+
//| Automatic non-optimal discretization, internal subroutine.       |
//+------------------------------------------------------------------+
void CBdSS::DSSplitK(double &ca[],int &cc[],const int n,const int nc,
                     int kmax,int &info,double &thresholds[],int &ni,
                     double &cve)
  {
   CRowDouble A=ca;
   CRowInt C=cc;
   CRowDouble Thresholds=thresholds;
   DSSplitK(A,C,n,nc,kmax,info,Thresholds,ni,cve);
   Thresholds.ToArray(thresholds);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CBdSS::DSSplitK(CRowDouble &ca,CRowInt &cc,const int n,const int nc,
                     int kmax,int &info,CRowDouble &thresholds,int &ni,
                     double &cve)
  {
//--- create variables
   int    i=0;
   int    j=0;
   int    j1=0;
   int    k=0;
   int    tiecount=0;
   double v2=0;
   int    bestk=0;
   double bestcve=0;
   double curcve=0;
//--- creating arrays
   CRowInt    ties;
   CRowInt    p1;
   CRowInt    p2;
   CRowInt    cnt;
   CRowInt    bestsizes;
   CRowInt    cursizes;
   CRowDouble a=ca;
   CRowInt    c=cc;
   a.Resize(n);
   c.Resize(n);
//--- initialization
   info=0;
   ni=0;
   cve=0;
//--- Test for errors in inputs
   if((n<=0 || nc<2) || kmax<2)
     {
      info=-1;
      return;
     }
   for(i=0; i<n; i++)
     {
      //--- check
      if(c[i]<0 || c[i]>=nc)
        {
         info=-2;
         return;
        }
     }
//--- change value
   info=1;
//--- Tie
   DSTie(a,n,ties,tiecount,p1,p2);
//--- swap
   for(i=0; i<n; i++)
     {
      //--- check
      if(p2[i]!=i)
         c.Swap(i,p2[i]);
     }
//--- Special cases
   if(tiecount==1)
     {
      info=-3;
      return;
     }
//--- General case:
//--- 0. allocate arrays
   kmax=MathMin(kmax,tiecount);
//--- allocation
   bestsizes.Resize(kmax);
   cursizes.Resize(kmax);
   cnt.Resize(nc);
//--- General case:
//--- 1. prepare "weak" solution (two subintervals,divided at median)
   v2=CMath::m_maxrealnumber;
   j=-1;
   for(i=1; i<tiecount; i++)
     {
      //--- check
      if(MathAbs(ties[i]-0.5*(n-1))<v2)
        {
         v2=MathAbs(ties[i]-0.5*n);
         j=i;
        }
     }
//--- check
   if(!CAp::Assert(j>0,__FUNCTION__+": internal error #1!"))
      return;
//--- change values
   bestk=2;
   bestsizes.Set(0,ties[j]);
   bestsizes.Set(1,n-j);
   bestcve=0;
//--- calculation
   cnt.Fill(0);
   for(i=0; i<j; i++)
      TieAddC(c,ties,i,nc,cnt);
   bestcve+=GetCV(cnt,nc);
//--- calculation
   cnt.Fill(0);
   for(i=j; i<=tiecount-1; i++)
      TieAddC(c,ties,i,nc,cnt);
   bestcve+=GetCV(cnt,nc);
//--- General case:
//--- 2. Use greedy algorithm to find sub-optimal split in O(KMax*N) time
   for(k=2; k<=kmax; k++)
     {
      //--- Prepare greedy K-interval split
      cursizes.Fill(0,0,k);
      //--- change values
      i=0;
      j=0;
      //--- cycle
      while(j<tiecount && i<k)
        {
         //--- Rule: I-th bin is empty,fill it
         if(cursizes[i]==0)
           {
            cursizes.Set(i,ties[j+1]-ties[j]);
            j++;
            continue;
           }
         //--- Rule: (K-1-I) bins left,(K-1-I) ties left (1 tie per bin);next bin
         if((tiecount-j)==(k-1-i))
           {
            i++;
            continue;
           }
         //--- Rule: last bin,always place in current
         if(i==(k-1))
           {
            cursizes.Add(i,ties[j+1]-ties[j]);
            j++;
            continue;
           }
         //--- Place J-th tie in I-th bin,or leave for I+1-th bin.
         if(MathAbs(cursizes[i]+ties[j+1]-ties[j]-(double)n/(double)k)<MathAbs(cursizes[i]-(double)n/(double)k))
           {
            cursizes.Add(i,ties[j+1]-ties[j]);
            j++;
           }
         else
            i++;
        }
      //--- check
      if(!CAp::Assert(cursizes[k-1]!=0 && j==tiecount,__FUNCTION__+": internal error #1"))
         return;
      //--- Calculate CVE
      curcve=0;
      j=0;
      for(i=0; i<k; i++)
        {
         //--- calculation
         cnt.Fill(0,0,nc);
         for(j1=j; j1<j+cursizes[i]; j1++)
            cnt.Add(c[j1],1);
         curcve+=GetCV(cnt,nc);
         j+=cursizes[i];
        }
      //--- Choose best variant
      if(curcve<bestcve)
        {
         bestsizes.Copy(cursizes,0,0,k);
         bestcve=curcve;
         bestk=k;
        }
     }
//--- Transform from sizes to thresholds
   cve=bestcve;
   ni=bestk;
//--- allocation
   thresholds.Resize(ni-1);
   j=bestsizes[0];
//--- calculation
   for(i=1; i<bestk; i++)
     {
      thresholds.Set(i-1,0.5*(a[j-1]+a[j]));
      j+=bestsizes[i];
     }
  }
//+------------------------------------------------------------------+
//| Automatic optimal discretization, internal subroutine.           |
//+------------------------------------------------------------------+
void CBdSS::DSOptimalSplitK(double &ca[],int &cc[],const int n,
                            const int nc,int kmax,int &info,
                            double &thresholds[],int &ni,double &cve)
  {
   CRowDouble A=ca;
   CRowInt C=cc;
   CRowDouble Thresholds=thresholds;
   DSOptimalSplitK(A,C,n,nc,kmax,info,Thresholds,ni,cve);
   Thresholds.ToArray(thresholds);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CBdSS::DSOptimalSplitK(CRowDouble &ca,CRowInt &cc,const int n,
                            const int nc,int kmax,int &info,
                            CRowDouble &thresholds,int &ni,double &cve)
  {
//--- Test for errors in inputs
   if((n<=0 || nc<2) || kmax<2)
     {
      info=-1;
      return;
     }
//--- create variables
   int    i=0;
   int    j=0;
   int    s=0;
   int    jl=0;
   int    jr=0;
   double v2=0;
   int    tiecount=0;
   double cvtemp=0;
   int    k=0;
   int    koptimal=0;
   double cvoptimal=0;
//--- creating arrays
   CRowInt    ties;
   CRowInt    p1;
   CRowInt    p2;
   CRowInt    cnt;
   CRowInt    cnt2;
//--- copy
   CRowDouble a=ca;
   CRowInt    c=cc;
   a.Resize(n);
   c.Resize(n);
//--- create matrix
   CMatrixDouble cv;
   CMatrixInt    splits;
//--- initialization
   info=0;
   ni=0;
   cve=0;
   for(i=0; i<n; i++)
     {
      //--- check
      if(c[i]<0 || c[i]>=nc)
        {
         info=-2;
         return;
        }
     }
//--- change value
   info=1;
//--- Tie
   DSTie(a,n,ties,tiecount,p1,p2);
//--- swap
   for(i=0; i<n; i++)
      //--- check
      if(p2[i]!=i)
         c.Swap(i,p2[i]);
//--- Special cases
   if(tiecount==1)
     {
      info=-3;
      return;
     }
//--- General case
//--- Use dynamic programming to find best split in O(KMax*NC*TieCount^2) time
   kmax=MathMin(kmax,tiecount);
//--- allocation
   cv.Resize(kmax,tiecount);
   splits.Resize(kmax,tiecount);
   cnt.Resize(nc);
   cnt2.Resize(nc);
//--- calculation
   cnt.Fill(0);
   for(j=0; j<tiecount; j++)
     {
      TieAddC(c,ties,j,nc,cnt);
      splits.Set(0,j,0);
      cv.Set(0,j,GetCV(cnt,nc));
     }
   for(k=1; k<=kmax-1; k++)
     {
      cnt.Fill(0);
      //--- Subtask size J in [K..TieCount-1]:
      //--- optimal K-splitting on ties from 0-th to J-th.
      for(j=k; j<=tiecount-1; j++)
        {
         //--- Update Cnt - let it contain classes of ties from K-th to J-th
         TieAddC(c,ties,j,nc,cnt);
         //--- Search for optimal split point S in [K..J]
         cnt2=cnt;
         cv.Set(k,j,cv.Get(k-1,j-1)+GetCV(cnt2,nc));
         splits.Set(k,j,j);
         //--- calculation
         for(s=k+1; s<=j; s++)
           {
            //--- Update Cnt2 - let it contain classes of ties from S-th to J-th
            TieSubC(c,ties,s-1,nc,cnt2);
            //--- Calculate CVE
            cvtemp=cv.Get(k-1,s-1)+GetCV(cnt2,nc);
            //--- check
            if(cvtemp<cv.Get(k,j))
              {
               cv.Set(k,j,cvtemp);
               splits.Set(k,j,s);
              }
           }
        }
     }
//--- Choose best partition,output result
   koptimal=-1;
   cvoptimal=CMath::m_maxrealnumber;
   for(k=0; k<kmax; k++)
     {
      //--- check
      if(cv.Get(k,tiecount-1)<cvoptimal)
        {
         cvoptimal=cv.Get(k,tiecount-1);
         koptimal=k;
        }
     }
//--- check
   if(!CAp::Assert(koptimal>=0,__FUNCTION__+": internal error #1!"))
      return;
//--- check
   if(koptimal==0)
     {
      //--- Special case: best partition is one big interval.
      //--- Even 2-partition is not better.
      //--- This is possible when dealing with "weak" predictor variables.
      //--- Make binary split as close to the median as possible.
      v2=CMath::m_maxrealnumber;
      j=-1;
      for(i=1; i<=tiecount-1; i++)
        {
         //--- check
         if(MathAbs(ties[i]-0.5*(n-1))<v2)
           {
            v2=MathAbs(ties[i]-0.5*(n-1));
            j=i;
           }
        }
      //--- check
      if(!CAp::Assert(j>0,__FUNCTION__+": internal error #2!"))
         return;
      //--- allocation
      thresholds.Resize(1);
      //--- change values
      thresholds.Set(0,0.5*(a[ties[j-1]]+a[ties[j]]));
      ni=2;
      cve=0;
      //--- calculation
      cnt.Fill(0);
      for(i=0; i<j; i++)
         TieAddC(c,ties,i,nc,cnt);
      cve+=GetCV(cnt,nc);
      cnt.Fill(0);
      for(i=j; i<=tiecount-1; i++)
         TieAddC(c,ties,i,nc,cnt);
      cve+=GetCV(cnt,nc);
     }
   else
     {
      //--- General case: 2 or more intervals
      thresholds.Resize(koptimal);
      ni=koptimal+1;
      cve=cv.Get(koptimal,tiecount-1);
      jl=splits.Get(koptimal,tiecount-1);
      jr=tiecount-1;
      //--- calculation
      for(k=koptimal; k>=1; k--)
        {
         thresholds.Set(k-1,0.5*(a[ties[jl-1]]+a[ties[jl]]));
         jr=jl-1;
         jl=splits.Get(k-1,jl-1);
        }
     }
  }
//+------------------------------------------------------------------+
//| Internal function                                                |
//+------------------------------------------------------------------+
double CBdSS::XLnY(const double x,const double y)
  {
//--- check
   if(x==0.0)
      return(0.0);
//--- return result
   return(x*MathLog(y));
  }
//+------------------------------------------------------------------+
//| Internal function,                                               |
//| returns number of samples of class I in Cnt[I]                   |
//+------------------------------------------------------------------+
double CBdSS::GetCV(CRowInt &cnt,const int nc)
  {
//--- create variables
   double result=0;
   int    i=0;
   double s=0;
//--- calculation
   s=0;
   for(i=0; i<nc; i++)
      s+=cnt[i];
//--- get result
   result=0;
   for(i=0; i<nc; i++)
      result-=XLnY(cnt[i],cnt[i]/(s+nc-1));
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| Internal function,adds number of samples of class I in tie NTie  |
//| to Cnt[I]                                                        |
//+------------------------------------------------------------------+
void CBdSS::TieAddC(CRowInt &c,CRowInt &ties,const int ntie,const int nc,
                    CRowInt &cnt)
  {
   for(int i=ties[ntie]; i<ties[ntie+1]; i++)
      cnt.Add(c[i],1);
  }
//+------------------------------------------------------------------+
//| Internal function,subtracts number of samples of class I in tie  |
//| NTie to Cnt[I]                                                   |
//+------------------------------------------------------------------+
void CBdSS::TieSubC(CRowInt &c,CRowInt &ties,const int ntie,const int nc,
                    CRowInt &cnt)
  {
   for(int i=ties[ntie]; i<ties[ntie+1]; i++)
      cnt.Add(c[i],-1);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
struct CDFWorkBuf
  {
   CRowInt           m_classpriors;
   CRowInt           m_varpool;
   int               m_varpoolsize;
   CRowInt           m_trnset;
   int               m_trnsize;
   CRowDouble        m_trnlabelsr;
   CRowInt           m_trnlabelsi;
   CRowInt           m_oobset;
   int               m_oobsize;
   CRowDouble        m_ooblabelsr;
   CRowInt           m_ooblabelsi;
   CRowDouble        m_treebuf;
   CRowDouble        m_curvals;
   CRowDouble        m_bestvals;
   CRowInt           m_tmp0i;
   CRowInt           m_tmp1i;
   CRowDouble        m_tmp0r;
   CRowDouble        m_tmp1r;
   CRowDouble        m_tmp2r;
   CRowDouble        m_tmp3r;
   CRowInt           m_tmpnrms2;
   CRowInt           m_classtotals0;
   CRowInt           m_classtotals1;
   CRowInt           m_classtotals01;
   //--- constructor / destructor
                     CDFWorkBuf(void) { m_trnsize=0; m_oobsize=0; }
                    ~CDFWorkBuf(void) {}
   void              Copy(const CDFWorkBuf &obj);
   //--- overloading
   void              operator=(const CDFWorkBuf &obj) { Copy(obj); }
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CDFWorkBuf::Copy(const CDFWorkBuf &obj)
  {
   m_classpriors=obj.m_classpriors;
   m_varpool=obj.m_varpool;
   m_varpoolsize=obj.m_varpoolsize;
   m_trnset=obj.m_trnset;
   m_trnsize=obj.m_trnsize;
   m_trnlabelsr=obj.m_trnlabelsr;
   m_trnlabelsi=obj.m_trnlabelsi;
   m_oobset=obj.m_oobset;
   m_oobsize=obj.m_oobsize;
   m_ooblabelsr=obj.m_ooblabelsr;
   m_ooblabelsi=obj.m_ooblabelsi;
   m_treebuf=obj.m_treebuf;
   m_curvals=obj.m_curvals;
   m_bestvals=obj.m_bestvals;
   m_tmp0i=obj.m_tmp0i;
   m_tmp1i=obj.m_tmp1i;
   m_tmp0r=obj.m_tmp0r;
   m_tmp1r=obj.m_tmp1r;
   m_tmp2r=obj.m_tmp2r;
   m_tmp3r=obj.m_tmp3r;
   m_tmpnrms2=obj.m_tmpnrms2;
   m_classtotals0=obj.m_classtotals0;
   m_classtotals1=obj.m_classtotals1;
   m_classtotals01=obj.m_classtotals01;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
struct CDFVoteBuf
  {
   CRowDouble        m_trntotals;
   CRowDouble        m_oobtotals;
   CRowInt           m_trncounts;
   CRowInt           m_oobcounts;
   CRowDouble        m_giniimportances;
   //--- constructor / destructor
                     CDFVoteBuf(void) {}
                    ~CDFVoteBuf(void) {}
   //---
   void              Copy(const CDFVoteBuf &obj);
   //--- overloading
   void              operator=(const CDFVoteBuf &obj) { Copy(obj); }

  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CDFVoteBuf::Copy(const CDFVoteBuf &obj)
  {
   m_trntotals=obj.m_trntotals;
   m_oobtotals=obj.m_oobtotals;
   m_trncounts=obj.m_trncounts;
   m_oobcounts=obj.m_oobcounts;
   m_giniimportances=obj.m_giniimportances;
  }
//+------------------------------------------------------------------+
//| Permutation importance buffer object, stores permutation -       |
//| related losses for some subset of the dataset + some temporaries |
//|   Losses      -  array[NVars + 2], stores sum of squared         |
//|                  residuals for each permutation type:            |
//|                  * Losses[0..NVars - 1] stores losses for        |
//|                    permutation in J-th variable                  |
//|                  * Losses[NVars] stores loss for all variables   |
//|                    being randomly perturbed                      |
//|                  * Losses[NVars + 1] stores loss for unperturbed |
//|                    dataset                                       |
//+------------------------------------------------------------------+
struct CDFPermimpBuf
  {
   CRowDouble        m_losses;
   CRowDouble        m_xraw;
   CRowDouble        m_xdist;
   CRowDouble        m_xcur;
   CRowDouble        m_y;
   CRowDouble        m_yv;
   CRowDouble        m_targety;
   CRowInt           m_startnodes;
   //--- constructor / destructor
                     CDFPermimpBuf(void) {}
                    ~CDFPermimpBuf(void) {}
   //---
   void              Copy(const CDFPermimpBuf &obj);
   //--- overloading
   void              operator=(const CDFPermimpBuf &obj) { Copy(obj); }
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CDFPermimpBuf::Copy(const CDFPermimpBuf &obj)
  {
   m_losses=obj.m_losses;
   m_xraw=obj.m_xraw;
   m_xdist=obj.m_xdist;
   m_xcur=obj.m_xcur;
   m_y=obj.m_y;
   m_yv=obj.m_yv;
   m_targety=obj.m_targety;
   m_startnodes=obj.m_startnodes;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
struct CDFTreeBuf
  {
   CRowDouble        m_treebuf;
   int               m_treeidx;
   //--- constructor / destructor
                     CDFTreeBuf(void) { m_treeidx=-1; }
                    ~CDFTreeBuf(void) {}
   //---
   void              Copy(const CDFTreeBuf &obj);
   //--- overloading
   void              operator=(const CDFTreeBuf &obj) { Copy(obj); }
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CDFTreeBuf::Copy(const CDFTreeBuf &obj)
  {
   m_treebuf=obj.m_treebuf;
   m_treeidx=obj.m_treeidx;
  }
//+------------------------------------------------------------------+
//| A random forest (decision forest) builder object.                |
//| Used to store dataset and specify decision forest training       |
//| algorithm settings.                                              |
//+------------------------------------------------------------------+
struct CDecisionForestBuilder
  {
   int               m_DSType;
   int               m_NPoints;
   int               m_NVars;
   int               m_NClasses;
   CRowDouble        m_DSData;
   CRowDouble        m_DSRVal;
   CRowInt           m_DSIVal;
   int               m_RDFAlgo;
   double            m_RDFRatio;
   double            m_RDFVars;
   int               m_RDFGlobalSeed;
   int               m_RDFSplitStrength;
   int               m_RDFImportance;
   CRowDouble        m_DSMin;
   CRowDouble        m_DSMax;
   bool              m_DSBinary[];
   double            m_DSRAvg;
   CRowInt           m_DSCTotals;
   int               m_RDFProgress;
   int               m_RDFTotal;
   bool              m_NeedIOBMatrix;
   CMatrixInt        m_IOBMatrix;
   CRowInt           m_VarImpShuffle2;
   CDFWorkBuf        m_WorkBuf[];
   CDFVoteBuf        m_VoteBuf[];
   CDFTreeBuf        m_TreeBuf[];
   //--- constructor / destructor
                     CDecisionForestBuilder(void);
                    ~CDecisionForestBuilder(void) {}
   //---
   void              Copy(const CDecisionForestBuilder &obj);
   //--- overloading
   void              operator=(const CDecisionForestBuilder &obj) { Copy(obj); }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CDecisionForestBuilder::CDecisionForestBuilder(void)
  {
   m_DSType=0;
   m_NPoints=0;
   m_NVars=0;
   m_NClasses=0;
   m_RDFAlgo=0;
   m_RDFRatio=0;
   m_RDFVars=0;
   m_RDFGlobalSeed=0;
   m_RDFSplitStrength=0;
   m_RDFImportance=0;
   m_DSRAvg=0;
   m_RDFProgress=0;
   m_RDFTotal=0;
   m_NeedIOBMatrix=false;
  }
//+------------------------------------------------------------------+
//| Copy                                                             |
//+------------------------------------------------------------------+
void CDecisionForestBuilder::Copy(const CDecisionForestBuilder &obj)
  {
   m_DSType=obj.m_DSType;;
   m_NPoints=obj.m_NPoints;
   m_NVars=obj.m_NVars;
   m_NClasses=obj.m_NClasses;
   m_DSData=obj.m_DSData;
   m_DSRVal=obj.m_DSRVal;
   m_DSIVal=obj.m_DSIVal;
   m_RDFAlgo=obj.m_RDFAlgo;
   m_RDFRatio=obj.m_RDFRatio;
   m_RDFVars=obj.m_RDFVars;
   m_RDFGlobalSeed=obj.m_RDFGlobalSeed;
   m_RDFSplitStrength=obj.m_RDFSplitStrength;
   m_RDFImportance=obj.m_RDFImportance;
   m_DSMin=obj.m_DSMin;
   m_DSMax=obj.m_DSMax;
   ArrayCopy(m_DSBinary,obj.m_DSBinary);
   m_DSRAvg=obj.m_DSRAvg;
   m_DSCTotals=obj.m_DSCTotals;
   m_RDFProgress=obj.m_RDFProgress;
   m_RDFTotal=obj.m_RDFTotal;
   m_NeedIOBMatrix=obj.m_NeedIOBMatrix;
   m_IOBMatrix=obj.m_IOBMatrix;
   m_VarImpShuffle2=obj.m_VarImpShuffle2;
   int total=ArraySize(obj.m_WorkBuf);
   ArrayResize(m_WorkBuf,total);
   ArrayResize(m_VoteBuf,total);
   ArrayResize(m_TreeBuf,total);
   for(int i=0; i<total; i++)
     {
      m_WorkBuf[i]=obj.m_WorkBuf[i];
      m_VoteBuf[i]=obj.m_VoteBuf[i];
      m_TreeBuf[i]=obj.m_TreeBuf[i];
     }
  }
//+------------------------------------------------------------------+
//| Buffer object which is used to perform various requests(usually  |
//| model inference) in the multithreaded mode(multiple threads      |
//| working with same DF object).                                    |
//| This object should be created with DFCreateBuffer().             |
//+------------------------------------------------------------------+
struct CDecisionForestBuffer
  {
   CRowDouble        m_x;
   CRowDouble        m_y;
   //--- constructor / destructor
                     CDecisionForestBuffer(void) {}
                    ~CDecisionForestBuffer(void) {}
   //---
   void              Copy(const CDecisionForestBuffer &obj);
   //--- overloading
   void              operator=(const CDecisionForestBuffer &obj) { Copy(obj); }
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CDecisionForestBuffer::Copy(const CDecisionForestBuffer &obj)
  {
   m_x=obj.m_x;
   m_y=obj.m_y;
  }
//+------------------------------------------------------------------+
//| Decision forest (random forest) model.                           |
//+------------------------------------------------------------------+
class CDecisionForest
  {
public:
   int               m_ForestFormat;
   bool              m_UseMantissa8;
   int               m_NVars;
   int               m_NClasses;
   int               m_NTrees;
   int               m_BufSize;
   CRowDouble        m_Trees;
   CDecisionForestBuffer m_Buffer;
   CRowInt           m_Trees8;
   //--- constructor, destructor
                     CDecisionForest(void) { m_ForestFormat=0; m_UseMantissa8=false; m_NVars=0; m_NClasses=0; m_NTrees=0; m_BufSize=0; }
                    ~CDecisionForest(void) {}
   //--- copy
   void              Copy(const CDecisionForest &obj);
   //--- overloading
   void              operator=(const CDecisionForest &obj) { Copy(obj); }
  };
//+------------------------------------------------------------------+
//| Copy                                                             |
//+------------------------------------------------------------------+
void CDecisionForest::Copy(const CDecisionForest &obj)
  {
//--- copy variables
   m_ForestFormat=obj.m_ForestFormat;
   m_UseMantissa8=obj.m_UseMantissa8;
   m_NVars=obj.m_NVars;
   m_NClasses=obj.m_NClasses;
   m_NTrees=obj.m_NTrees;
   m_BufSize=obj.m_BufSize;
   m_Trees=obj.m_Trees;
   m_Buffer=obj.m_Buffer;
   m_Trees8=obj.m_Trees8;
  }
//+------------------------------------------------------------------+
//| This class is a shell for class CDecisionForest                  |
//+------------------------------------------------------------------+
class CDecisionForestShell
  {
private:
   CDecisionForest   m_innerobj;

public:
   //--- constructors, destructor
                     CDecisionForestShell(void) {}
                     CDecisionForestShell(CDecisionForest &obj) { m_innerobj.Copy(obj); }
                    ~CDecisionForestShell(void) {}
   //--- method
   CDecisionForest  *GetInnerObj(void) { return(GetPointer(m_innerobj)); }
  };
//+------------------------------------------------------------------+
//| Decision forest training report.                                 |
//| === training/oob errors ======================================== |
//| Following fields store training set errors:                      |
//|   * relclserror     -  fraction of misclassified cases, [0,1]    |
//|   * avgce           -  average cross-entropy in bits per symbol  |
//|   * rmserror        -  root-mean-square error                    |
//|   * avgerror        -  average error                             |
//|   * avgrelerror     -  average relative error                    |
//| Out-of-bag estimates are stored in fields with same names, but   |
//| "oob" prefix.                                                    |
//| For classification problems:                                     |
//|   * RMS, AVG and AVGREL errors are calculated for posterior      |
//|     probabilities                                                |
//| For regression problems:                                         |
//|   * RELCLS and AVGCE errors are zero                             |
//| === variable importance ======================================== |
//| Following fields are used to store variable importance           |
//| information:                                                     |
//|   * topvars         -  variables ordered from the most important |
//|                        to less important ones (according to      |
//|                        current choice of importance raiting).    |
//|                        For example, topvars[0] contains index of |
//|                        the most important variable, and          |
//|                        topvars[0:2] are indexes of 3 most        |
//|                        important ones and so on.                 |
//|   * varimportances  -  array[nvars], ratings (the larger, the    |
//|                        more important the variable is, always in |
//|                        [0,1] range). By default, filled by zeros |
//|                        (no importance ratings are provided unless|
//|                        you explicitly request them). Zero rating |
//|                        means that variable is not important,     |
//|                        however you will rarely encounter such    |
//|                        a  thing,  in  many  cases  unimportant   |
//|                        variables produce nearly-zero (but        |
//|                        nonzero) ratings. Variable importance     |
//|                        report must be EXPLICITLY requested by    |
//|                        calling:                                  |
//|                        * DFBuilderSetImportanceGini() function,  |
//|                          if you need out-of-bag Gini-based       |
//|                          importance rating also known as MDI     |
//|                          (fast to calculate, resistant to        |
//|                          overfitting issues, but has some bias   |
//|                          towards continuous and high-cardinality |
//|                          categorical variables)                  |
//|                        * DFBuilderSetImportanceTrnGini() function|
//|                          if you need training set Gini-based     |
//|                          importance rating (what other packages  |
//|                          typically report).                      |
//|                        * DFBuilderSetImportancePermutation()     |
//|                          function, if you need permutation-based |
//|                          importance rating also known as MDA     |
//|                          (slower to calculate, but less biased)  |
//|                        * DFBuilderSetImportancenOne() function,  |
//|                          if you do not need importance ratings - |
//|                          ratings will be zero, topvars[] will be |
//|                          [0,1,2,...]                             |
//| Different importance ratings (Gini or permutation) produce       |
//| non-comparable values. Although in all cases rating values lie in|
//| [0,1] range, there  are exist differences:                       |
//|   * informally speaking, Gini importance rating tends to divide  |
//|     "unit amount of importance" between several important        |
//|     variables, i.e. it produces estimates which roughly sum      |
//|     to 1.0 (or less than 1.0, if your task can not be solved     |
//|     exactly). If all variables are equally important, they will  |
//|     have same rating, roughly 1/NVars, even if every variable is |
//|     critically important.                                        |
//|   * from the other side, permutation importance tells us what    |
//|     percentage of the model predictive power will be ruined by   |
//|     permuting this specific variable. It does not produce        |
//|     estimates which sum to one. Critically important variable    |
//|     will have rating close to 1.0, and you may have multiple     |
//|     variables with such a rating.                                |
//| More information on variable importance ratings can be found in  |
//| comments on the DFBuilderSetImportanceGini() and                 |
//| DFBuilderSetImportancePermutation() functions.                   |
//+------------------------------------------------------------------+
class CDFReport
  {
public:
   //--- variables
   double            m_RelCLSError;
   double            m_AvgCE;
   double            m_RMSError;
   double            m_AvgError;
   double            m_AvgRelError;
   double            m_oobrelclserror;
   double            m_oobavgce;
   double            m_oobrmserror;
   double            m_oobavgerror;
   double            m_oobavgrelerror;
   CRowInt           m_topvars;
   CRowDouble        m_varimportances;
   //--- constructor, destructor
                     CDFReport(void);
                    ~CDFReport(void) {}
   //--- copy
   void              Copy(const CDFReport &obj);
   //--- overloading
   void              operator=(const CDFReport &obj) { Copy(obj); }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CDFReport::CDFReport(void)
  {
   m_RelCLSError=0;
   m_AvgCE=0;
   m_RMSError=0;
   m_AvgError=0;
   m_AvgRelError=0;
   m_oobrelclserror=0;
   m_oobavgce=0;
   m_oobrmserror=0;
   m_oobavgerror=0;
   m_oobavgrelerror=0;
  }
//+------------------------------------------------------------------+
//| Copy                                                             |
//+------------------------------------------------------------------+
void CDFReport::Copy(const CDFReport &obj)
  {
//--- copy variables
   m_RelCLSError=obj.m_RelCLSError;
   m_AvgCE=obj.m_AvgCE;
   m_RMSError=obj.m_RMSError;
   m_AvgError=obj.m_AvgError;
   m_AvgRelError=obj.m_AvgRelError;
   m_oobrelclserror=obj.m_oobrelclserror;
   m_oobavgce=obj.m_oobavgce;
   m_oobrmserror=obj.m_oobrmserror;
   m_oobavgerror=obj.m_oobavgerror;
   m_oobavgrelerror=obj.m_oobavgrelerror;
   m_topvars=obj.m_topvars;
   m_varimportances=obj.m_varimportances;
  }
//+------------------------------------------------------------------+
//| This class is a shell for class CDFReport                        |
//+------------------------------------------------------------------+
class CDFReportShell
  {
private:
   CDFReport         m_innerobj;

public:
   //--- constructors, destructor
                     CDFReportShell(void) {}
                     CDFReportShell(CDFReport &obj) { m_innerobj.Copy(obj); }
                    ~CDFReportShell(void) {}
   //--- methods
   double            GetRelClsError(void);
   void              SetRelClsError(const double d);
   double            GetAvgCE(void);
   void              SetAvgCE(const double d);
   double            GetRMSError(void);
   void              SetRMSError(const double d);
   double            GetAvgError(void);
   void              SetAvgError(const double d);
   double            GetAvgRelError(void);
   void              SetAvgRelError(const double d);
   double            GetOOBRelClsError(void);
   void              SetOOBRelClsError(const double d);
   double            GetOOBAvgCE(void);
   void              SetOOBAvgCE(const double d);
   double            GetOOBRMSError(void);
   void              SetOOBRMSError(const double d);
   double            GetOOBAvgError(void);
   void              SetOOBAvgError(const double d);
   double            GetOOBAvgRelError(void);
   void              SetOOBAvgRelError(const double d);
   CDFReport        *GetInnerObj(void);
  };
//+------------------------------------------------------------------+
//| Returns the value of the variable relclserror                    |
//+------------------------------------------------------------------+
double CDFReportShell::GetRelClsError(void)
  {
   return(m_innerobj.m_RelCLSError);
  }
//+------------------------------------------------------------------+
//| Changing the value of the variable relclserror                   |
//+------------------------------------------------------------------+
void CDFReportShell::SetRelClsError(const double d)
  {
   m_innerobj.m_RelCLSError=d;
  }
//+------------------------------------------------------------------+
//| Returns the value of the variable avgce                          |
//+------------------------------------------------------------------+
double CDFReportShell::GetAvgCE(void)
  {
   return(m_innerobj.m_AvgCE);
  }
//+------------------------------------------------------------------+
//| Changing the value of the variable avgce                         |
//+------------------------------------------------------------------+
void CDFReportShell::SetAvgCE(const double d)
  {
   m_innerobj.m_AvgCE=d;
  }
//+------------------------------------------------------------------+
//| Returns the value of the variable rmserror                       |
//+------------------------------------------------------------------+
double CDFReportShell::GetRMSError(void)
  {
   return(m_innerobj.m_RMSError);
  }
//+------------------------------------------------------------------+
//| Changing the value of the variable rmserror                      |
//+------------------------------------------------------------------+
void CDFReportShell::SetRMSError(const double d)
  {
   m_innerobj.m_RMSError=d;
  }
//+------------------------------------------------------------------+
//| Returns the value of the variable avgerror                       |
//+------------------------------------------------------------------+
double CDFReportShell::GetAvgError(void)
  {
   return(m_innerobj.m_AvgError);
  }
//+------------------------------------------------------------------+
//| Changing the value of the variable avgerror                      |
//+------------------------------------------------------------------+
void CDFReportShell::SetAvgError(const double d)
  {
   m_innerobj.m_AvgError=d;
  }
//+------------------------------------------------------------------+
//| Returns the value of the variable avgrelerror                    |
//+------------------------------------------------------------------+
double CDFReportShell::GetAvgRelError(void)
  {
   return(m_innerobj.m_AvgRelError);
  }
//+------------------------------------------------------------------+
//| Changing the value of the variable avgrelerror                   |
//+------------------------------------------------------------------+
void CDFReportShell::SetAvgRelError(const double d)
  {
   m_innerobj.m_AvgRelError=d;
  }
//+------------------------------------------------------------------+
//| Returns the value of the variable oobrelclserror                 |
//+------------------------------------------------------------------+
double CDFReportShell::GetOOBRelClsError(void)
  {
   return(m_innerobj.m_oobrelclserror);
  }
//+------------------------------------------------------------------+
//| Changing the value of the variable oobrelclserror                |
//+------------------------------------------------------------------+
void CDFReportShell::SetOOBRelClsError(const double d)
  {
   m_innerobj.m_oobrelclserror=d;
  }
//+------------------------------------------------------------------+
//| Returns the value of the variable oobavgce                       |
//+------------------------------------------------------------------+
double CDFReportShell::GetOOBAvgCE(void)
  {
   return(m_innerobj.m_oobavgce);
  }
//+------------------------------------------------------------------+
//| Changing the value of the variable oobavgce                      |
//+------------------------------------------------------------------+
void CDFReportShell::SetOOBAvgCE(const double d)
  {
   m_innerobj.m_oobavgce=d;
  }
//+------------------------------------------------------------------+
//| Returns the value of the variable oobrmserror                    |
//+------------------------------------------------------------------+
double CDFReportShell::GetOOBRMSError(void)
  {
   return(m_innerobj.m_oobrmserror);
  }
//+------------------------------------------------------------------+
//| Changing the value of the variable oobrmserror                   |
//+------------------------------------------------------------------+
void CDFReportShell::SetOOBRMSError(const double d)
  {
   m_innerobj.m_oobrmserror=d;
  }
//+------------------------------------------------------------------+
//| Returns the value of the variable oobavgerror                    |
//+------------------------------------------------------------------+
double CDFReportShell::GetOOBAvgError(void)
  {
   return(m_innerobj.m_oobavgerror);
  }
//+------------------------------------------------------------------+
//| Changing the value of the variable oobavgerror                   |
//+------------------------------------------------------------------+
void CDFReportShell::SetOOBAvgError(const double d)
  {
   m_innerobj.m_oobavgerror=d;
  }
//+------------------------------------------------------------------+
//| Returns the value of the variable oobavgrelerror                 |
//+------------------------------------------------------------------+
double CDFReportShell::GetOOBAvgRelError(void)
  {
   return(m_innerobj.m_oobavgrelerror);
  }
//+------------------------------------------------------------------+
//| Changing the value of the variable oobavgrelerror                |
//+------------------------------------------------------------------+
void CDFReportShell::SetOOBAvgRelError(const double d)
  {
   m_innerobj.m_oobavgrelerror=d;
  }
//+------------------------------------------------------------------+
//| Return object of class                                           |
//+------------------------------------------------------------------+
CDFReport *CDFReportShell::GetInnerObj(void)
  {
   return(GetPointer(m_innerobj));
  }
//+------------------------------------------------------------------+
//| Auxiliary class for CDForest                                     |
//+------------------------------------------------------------------+
struct CDFInternalBuffers
  {
   //--- arrays
   CRowDouble        m_treebuf;
   CRowInt           m_idxbuf;
   CRowDouble        m_tmpbufr;
   CRowDouble        m_tmpbufr2;
   CRowInt           m_tmpbufi;
   CRowInt           m_classibuf;
   CRowDouble        m_sortrbuf;
   CRowDouble        m_sortrbuf2;
   CRowInt           m_sortibuf;
   CRowInt           m_varpool;
   bool              m_evsbin[];
   CRowDouble        m_evssplits;
   //--- constructor, destructor
                     CDFInternalBuffers(void) {}
                    ~CDFInternalBuffers(void) {}
   //---
   void              Copy(const CDFInternalBuffers &obj);
   //--- overloading
   void              operator=(const CDFInternalBuffers &obj) { Copy(obj); }
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CDFInternalBuffers::Copy(const CDFInternalBuffers &obj)
  {
//---
   m_treebuf=obj.m_treebuf;
   m_idxbuf=obj.m_idxbuf;
   m_tmpbufr=obj.m_tmpbufr;
   m_tmpbufr2=obj.m_tmpbufr2;
   m_tmpbufi=obj.m_tmpbufi;
   m_classibuf=obj.m_classibuf;
   m_sortrbuf=obj.m_sortrbuf;
   m_sortrbuf2=obj.m_sortrbuf2;
   m_sortibuf=obj.m_sortibuf;
   m_varpool=obj.m_varpool;
   m_evssplits=obj.m_evssplits;
//---
   ArrayCopy(m_evsbin,obj.m_evsbin);
  }
//+------------------------------------------------------------------+
//| Decision forest class                                            |
//+------------------------------------------------------------------+
class CDForest
  {
public:
   //--- class constants
   static const int  m_InnerNodeWidth;
   static const int  m_LeafNodeWdth;
   static const int  m_DFUseStrongSplits;
   static const int  m_DFUseEVS;
   static const int  m_DFUncompressedV0;
   static const int  m_DFCompressedV0;
   static const int  m_NeedTrnGini;
   static const int  m_NeedOOBGini;
   static const int  m_NeedPermutation;
   static const int  m_PermutationImportanceBatchSize;

   //--- public methods
   static void       DFCreateBuffer(CDecisionForest &model,CDecisionForestBuffer &buf);
   static void       DFBuilderCreate(CDecisionForestBuilder &s);
   static void       DFBuilderSetDataset(CDecisionForestBuilder &s,CMatrixDouble &xy,int npoints,int nvars,int nclasses);
   static void       DFBuilderSetRndVars(CDecisionForestBuilder &s,int rndvars);
   static void       DFBuilderSetRndVarsRatio(CDecisionForestBuilder &s,double f);
   static void       DFBuilderSetRndVarsAuto(CDecisionForestBuilder &s);
   static void       DFBuilderSetSubsampleRatio(CDecisionForestBuilder &s,double f);
   static void       DFBuilderSetSeed(CDecisionForestBuilder &s,int seedval);
   static void       DFBuilderSetRDFAlgo(CDecisionForestBuilder &s,int algotype);
   static void       DFBuilderSetRDFSplitStrength(CDecisionForestBuilder &s,int splitstrength);
   static void       DFBuilderSetImportanceTrnGini(CDecisionForestBuilder &s);
   static void       DFBuilderSetImportanceOOBGini(CDecisionForestBuilder &s);
   static void       DFBuilderSetImportancePermutation(CDecisionForestBuilder &s);
   static void       DFBuilderSetImportanceNone(CDecisionForestBuilder &s);
   static double     DFBuilderGetProgress(CDecisionForestBuilder &s);
   static double     DFBuilderPeekProgress(CDecisionForestBuilder &s);
   static void       DFBuilderBuildRandomForest(CDecisionForestBuilder &s,int ntrees,CDecisionForest &df,CDFReport &rep);
   static double     DFBinaryCompression(CDecisionForest &df);
   static double     DFBinaryCompression8(CDecisionForest &df);
   static void       DFProcess(CDecisionForest &df,double &x[],double &y[]);
   static void       DFProcess(CDecisionForest &df,CRowDouble &x,CRowDouble &y);
   static void       DFProcessI(CDecisionForest &df,double &x[],double &y[]);
   static void       DFProcessI(CDecisionForest &df,CRowDouble &x,CRowDouble &y);
   static double     DFProcess0(CDecisionForest &model,CRowDouble &x);
   static int        DFClassify(CDecisionForest &model,CRowDouble &x);
   static double     DFRelClsError(CDecisionForest &df,CMatrixDouble &xy,const int npoints);
   static double     DFAvgCE(CDecisionForest &df,CMatrixDouble &xy,const int npoints);
   static double     DFRMSError(CDecisionForest &df,CMatrixDouble &xy,const int npoints);
   static double     DFAvgError(CDecisionForest &df,CMatrixDouble &xy,const int npoints);
   static double     DFAvgRelError(CDecisionForest &df,CMatrixDouble &xy,const int npoints);
   static void       DFCopy(CDecisionForest &df1,CDecisionForest &df2);
   static void       DFAlloc(CSerializer &s,CDecisionForest &forest);
   static void       DFSerialize(CSerializer &s,CDecisionForest &forest);
   static void       DFUnserialize(CSerializer &s,CDecisionForest &forest);
   //---
   static void       DFBuildRandomDecisionForest(CMatrixDouble &xy,int npoints,int nvars,int nclasses,int ntrees,double r,int &info,CDecisionForest &df,CDFReport &rep);
   static void       DFBuildRandomDecisionForestX1(CMatrixDouble &xy,int npoints,int nvars,int nclasses,int ntrees,int nrndvars,double r,int &info,CDecisionForest &df,CDFReport &rep);
   static void       DFBuildInternal(CMatrixDouble &xy,int npoints,int nvars,int nclasses,int ntrees,int samplesize,int nfeatures,int flags,int &info,CDecisionForest &df,CDFReport &rep);

private:
   static void       BuildRandomTree(CDecisionForestBuilder &s,int treeidx0,int treeidx1);
   static void       BuildRandomTreeRec(CDecisionForestBuilder &s,CDFWorkBuf &workbuf,int workingset,int varstoselect,CRowDouble &treebuf,CDFVoteBuf &votebuf,CHighQualityRandState &rs,int idx0,int idx1,int oobidx0,int oobidx1,double meanloss,double topmostmeanloss,int &treesize);
   static void       EstimateVariableImportance(CDecisionForestBuilder &s,int sessionseed,CDecisionForest &df,int ntrees,CDFReport &rep);
   static void       EstimatePermutationImportances(CDecisionForestBuilder &s,CDecisionForest &df,int ntrees,CDFPermimpBuf &permimpbuf,int idx0,int idx1);
   static void       CleanReport(CDecisionForestBuilder &s,CDFReport &rep);
   static double     MeanNRMS2(int nclasses,CRowInt &trnlabelsi,CRowDouble &trnlabelsr,int trnidx0,int trnidx1,CRowInt &tstlabelsi,CRowDouble &tstlabelsr,int tstidx0,int tstidx1,CRowInt &tmpi);
   static void       ChooseCurrentSplitDense(CDecisionForestBuilder &s,CDFWorkBuf &workbuf,int &varsinpool,int varstoselect,CHighQualityRandState &rs,int idx0,int idx1,int &varbest,double &splitbest);
   static void       EvaluateDenseSplit(CDecisionForestBuilder &s,CDFWorkBuf &workbuf,CHighQualityRandState &rs,int splitvar,int idx0,int idx1,int &info,double &split,double &rms);
   static void       ClassifierSplit(CDecisionForestBuilder &s,CDFWorkBuf &workbuf,CRowDouble &x,CRowInt &c,int n,CHighQualityRandState &rs,int &info,double &threshold,double &e,CRowDouble &sortrbuf,CRowInt &sortibuf);
   static void       RegressionSplit(CDecisionForestBuilder &s,CDFWorkBuf &workbuf,CRowDouble &x,CRowDouble &y,int n,int &info,double &threshold,double &e,CRowDouble &sortrbuf,CRowDouble &sortrbuf2);
   static double     GetSplit(CDecisionForestBuilder &s,double a,double b,CHighQualityRandState &rs);
   static void       OutputLeaf(CDecisionForestBuilder &s,CDFWorkBuf &workbuf,CRowDouble &treebuf,CDFVoteBuf &votebuf,int idx0,int idx1,int oobidx0,int oobidx1,int &treesize,double leafval);
   static void       AnalyzeAndPreprocessDataset(CDecisionForestBuilder &s);
   static void       MergeTrees(CDecisionForestBuilder &s,CDecisionForest &df);
   static void       ProcessVotingResults(CDecisionForestBuilder &s,int ntrees,CDFVoteBuf &buf,CDFReport &rep);
   static double     BinaryCompression(CDecisionForest &df,bool usemantissa8);
   static int        ComputeCompressedSizeRec(CDecisionForest &df,bool usemantissa8,int treeroot,int treepos,CRowInt &compressedsizes,bool savecompressedsizes);
   static void       CompressRec(CDecisionForest &df,bool usemantissa8,int treeroot,int treepos,CRowInt &compressedsizes,CRowInt &buf,int &dstoffs);
   static int        ComputeCompressedUintSize(int v);
   static void       StreamUint(CRowInt &buf,int &offs,int v);
   static int        UnstreamUint(CRowInt &buf,int &offs);
   static void       StreamFloat(CRowInt &buf,bool usemantissa8,int &offs,double v);
   static double     UnstreamFloat(CRowInt &buf,bool usemantissa8,int &off);

   static int        DFClsError(CDecisionForest &df,CMatrixDouble &xy,const int npoints);
   static void       DFProcessInternalUncompressed(CDecisionForest &df,int subtreeroot,int nodeoffs,CRowDouble &x,CRowDouble &y);
   static void       DFProcessInternalCompressed(CDecisionForest &df,int offs,CRowDouble &x,CRowDouble &y);
  };
//+------------------------------------------------------------------+
//| Initialize constants                                             |
//+------------------------------------------------------------------+
const int CDForest::m_InnerNodeWidth=3;
const int CDForest::m_LeafNodeWdth=2;
const int CDForest::m_DFUseStrongSplits=1;
const int CDForest::m_DFUseEVS=2;
const int CDForest::m_DFUncompressedV0=0;
const int CDForest::m_DFCompressedV0=1;
const int CDForest::m_NeedTrnGini=1;
const int CDForest::m_NeedOOBGini=2;
const int CDForest::m_NeedPermutation=3;
const int CDForest::m_PermutationImportanceBatchSize=512;
//+------------------------------------------------------------------+
//| This function creates buffer structure which can be used to      |
//| perform parallel inference requests.                             |
//| DF subpackage provides two sets of computing functions - ones    |
//| which use internal buffer of DF model (these functions are       |
//| single-threaded because they use same buffer, which can not      |
//| shared between threads), and ones which use external buffer.     |
//| This function is used to initialize external buffer.             |
//| INPUT PARAMETERS:                                                |
//|   Model    -  DF model which is associated with newly created    |
//|               buffer                                             |
//| OUTPUT PARAMETERS:                                               |
//|   Buf      -  external buffer.                                   |
//| IMPORTANT: buffer object should be used only with model which was|
//|            used to initialize buffer. Any attempt to use buffer  |
//|            with different object is dangerous - you may get      |
//|            integrity check failure (exception) because sizes of  |
//|            internal arrays do not fit to dimensions of the model |
//|            structure.                                            |
//+------------------------------------------------------------------+
void CDForest::DFCreateBuffer(CDecisionForest &model,
                              CDecisionForestBuffer &buf)
  {
   buf.m_x.Resize(model.m_NVars);
   buf.m_y.Resize(model.m_NClasses);
  }
//+------------------------------------------------------------------+
//| This subroutine creates CDecisionForestBuilder object which is   |
//| used to train decision forests.                                  |
//| By default, new builder stores empty dataset and some reasonable |
//| default settings. At the very least, you should specify dataset  |
//| prior to building decision forest. You can also tweak settings of|
//| the forest construction algorithm (recommended, although default |
//| setting should work well).                                       |
//| Following actions are mandatory:                                 |
//|   * calling DFBuilderSetDataset() to specify dataset             |
//|   * calling DFBuilderBuildRandomForest() to build decision forest|
//|     using current dataset and default settings                   |
//| Additionally, you may call:                                      |
//|   * DFBuilderSetRndVars() or DFBuilderSetRndVarsRatio() to       |
//|     specify number of variables randomly chosen for each split   |
//|   * DFBuilderSetSubsampleRatio() to specify fraction of the      |
//|     dataset randomly subsampled to build each tree               |
//|   * DFBuilderSetSeed() to control random seed chosen for tree    |
//|     construction                                                 |
//| INPUT PARAMETERS:                                                |
//|   none                                                           |
//| OUTPUT PARAMETERS:                                               |
//|   S        -  decision forest builder                            |
//+------------------------------------------------------------------+
void CDForest::DFBuilderCreate(CDecisionForestBuilder &s)
  {
//--- Empty dataset
   s.m_DSType=-1;
   s.m_NPoints=0;
   s.m_NVars=0;
   s.m_NClasses=1;
//--- Default training settings
   s.m_RDFAlgo=0;
   s.m_RDFRatio=0.5;
   s.m_RDFVars=0.0;
   s.m_RDFGlobalSeed=0;
   s.m_RDFSplitStrength=2;
   s.m_RDFImportance=0;
//--- Other fields
   s.m_RDFProgress=0;
   s.m_RDFTotal=1;
  }
//+------------------------------------------------------------------+
//| This subroutine adds dense dataset to the internal storage of the|
//| builder object. Specifying your dataset in the dense format means|
//| that the dense version of the forest construction algorithm will |
//| be invoked.                                                      |
//| INPUT PARAMETERS:                                                |
//|   S        -  decision forest builder object                     |
//|   XY       -  array[NPoints,NVars+1] (minimum size; actual size  |
//|               can be larger, only leading part is used anyway),  |
//|               dataset:                                           |
//|               * first NVars elements of each row store values of |
//|                 the independent variables                        |
//|               * last column store class number(in 0...NClasses-1)|
//|                 or real value of the dependent variable          |
//|   NPoints  -  number of rows in the dataset, NPoints>=1          |
//|   NVars    -  number of independent variables, NVars>=1          |
//|   NClasses -  indicates type of the problem being solved:        |
//|               * NClasses>=2 means that classification problem is |
//|                 solved (last column of the dataset stores class  |
//|                 number)                                          |
//|               * NClasses=1  means  that  regression  problem  is |
//|                 solved  (last  column  of  the  dataset  stores  |
//|                 variable value)                                  |
//| OUTPUT PARAMETERS:                                               |
//|   S        -  decision forest builder                            |
//+------------------------------------------------------------------+
void CDForest::DFBuilderSetDataset(CDecisionForestBuilder &s,
                                   CMatrixDouble &xy,
                                   int npoints,
                                   int nvars,
                                   int nclasses)
  {
//--- create variables
   int i=0;
   int j=0;
//--- Check parameters
   if(!CAp::Assert(npoints>=1,__FUNCTION__": npoints<1"))
      return;
   if(!CAp::Assert(nvars>=1,__FUNCTION__": nvars<1"))
      return;
   if(!CAp::Assert(nclasses>=1,__FUNCTION__": nclasses<1"))
      return;
   if(!CAp::Assert(xy.Rows()>=npoints,__FUNCTION__": rows(xy)<npoints"))
      return;
   if(!CAp::Assert(xy.Cols()>nvars,__FUNCTION__": cols(xy)<nvars+1"))
      return;
   if(!CAp::Assert(CApServ::IsFiniteMatrix(xy,npoints,nvars+1),__FUNCTION__": xy parameter contains INFs or NANs"))
      return;
//---
   if(nclasses>1)
      for(i=0; i<npoints; i++)
        {
         j=(int)MathRound(xy.Get(i,nvars));
         if(!CAp::Assert(j>=0 && j<nclasses,__FUNCTION__": last column of xy contains invalid class number"))
            return;
        }
//--- Set dataset
   s.m_DSType=0;
   s.m_NPoints=npoints;
   s.m_NVars=nvars;
   s.m_NClasses=nclasses;
   CApServ::RVectorSetLengthAtLeast(s.m_DSData,npoints*nvars);
   for(i=0; i<npoints; i++)
      for(j=0; j<nvars; j++)
         s.m_DSData.Set(j*npoints+i,xy.Get(i,j));
   if(nclasses>1)
     {
      CApServ::IVectorSetLengthAtLeast(s.m_DSIVal,npoints);
      for(i=0; i<npoints; i++)
         s.m_DSIVal.Set(i,(int)MathRound(xy.Get(i,nvars)));
     }
   else
      s.m_DSRVal=xy.Col(nvars)+0;
  }
//+------------------------------------------------------------------+
//| This function sets number of variables (in [1,NVars] range) used |
//| by decision forest construction algorithm.                       |
//| The default option is to use roughly sqrt(NVars) variables.      |
//| INPUT PARAMETERS:                                                |
//|   S        -  decision forest builder object                     |
//|   RndVars  -  number of randomly selected variables; values      |
//|               outside of [1,NVars] range are silently clipped.   |
//| OUTPUT PARAMETERS:                                               |
//|   S        -  decision forest builder                            |
//+------------------------------------------------------------------+
void CDForest::DFBuilderSetRndVars(CDecisionForestBuilder &s,
                                   int rndvars)
  {
   s.m_RDFVars=MathMax(rndvars,1);
  }
//+------------------------------------------------------------------+
//| This function sets number of variables used by decision forest   |
//| construction algorithm as a fraction of total variable count     |
//| (0,1) range.                                                     |
//| The default option is to use roughly sqrt(NVars) variables.      |
//| INPUT PARAMETERS:                                                |
//|   S        -  decision forest builder object                     |
//|   F        -  round(NVars*F) variables are selected              |
//| OUTPUT PARAMETERS:                                               |
//|   S        -  decision forest builder                            |
//+------------------------------------------------------------------+
void CDForest::DFBuilderSetRndVarsRatio(CDecisionForestBuilder &s,
                                        double f)
  {
   if(!CAp::Assert(MathIsValidNumber(f),__FUNCTION__": F is INF or NAN"))
      return;
   s.m_RDFVars=-MathMax(f,CMath::m_machineepsilon);
  }
//+------------------------------------------------------------------+
//| This function tells decision forest builder to automatically     |
//| choose number of variables used by decision forest construction  |
//| algorithm. Roughly sqrt(NVars) variables will be used.           |
//| INPUT PARAMETERS:                                                |
//|   S        -  decision forest builder object                     |
//| OUTPUT PARAMETERS:                                               |
//|   S        -  decision forest builder                            |
//+------------------------------------------------------------------+
void CDForest::DFBuilderSetRndVarsAuto(CDecisionForestBuilder &s)
  {
   s.m_RDFVars=0;
  }
//+------------------------------------------------------------------+
//| This function sets size of dataset subsample generated the       |
//| decision forest construction algorithm. Size is specified as a   |
//| fraction of  total  dataset size.                                |
//| The default option is to use 50% of the dataset for training,    |
//| 50% for the OOB estimates. You can decrease fraction F down to   |
//| 10%, 1% or  even  below in order to reduce overfitting.          |
//| INPUT PARAMETERS:                                                |
//|   S        -  decision forest builder object                     |
//|   F        -  fraction of the dataset to use, in (0,1] range.    |
//|               Values outside of this range will  be  silently    |
//|               clipped. At least one element is always selected   |
//|               for the training set.                              |
//| OUTPUT PARAMETERS:                                               |
//|   S        -  decision forest builder                            |
//+------------------------------------------------------------------+
void CDForest::DFBuilderSetSubsampleRatio(CDecisionForestBuilder &s,
                                          double f)
  {
   if(!CAp::Assert(MathIsValidNumber(f),__FUNCTION__": F is INF or NAN"))
      return;
   s.m_RDFRatio=MathMax(f,CMath::m_machineepsilon);
  }
//+------------------------------------------------------------------+
//| This  function  sets  seed  used  by  internal  RNG  for  random |
//| subsampling and random selection of variable subsets.            |
//| By  default  random  seed  is  used, i.e. every time you build   |
//| decision forest, we seed generator with new value obtained from  |
//| system-wide  RNG.  Thus,  decision  forest  builder  returns     |
//| non-deterministic results. You  can  change  such  behavior  by  |
//| specyfing fixed positive seed value.                             |
//| INPUT PARAMETERS:                                                |
//|   S        -  decision forest builder object                     |
//|   SeedVal  -  seed value:                                        |
//|               * positive values are used for seeding RNG with    |
//|                 fixed seed, i.e. subsequent runs on same data    |
//|                 will return same decision forests                |
//|               * non-positive seed means that random seed is used |
//|                 for every run of builder, i.e. subsequent  runs  |
//|                 on same datasets will return slightly different  |
//|                 decision forests                                 |
//| OUTPUT PARAMETERS:                                               |
//|   S        -  decision forest builder, see                       |
//+------------------------------------------------------------------+
void CDForest::DFBuilderSetSeed(CDecisionForestBuilder &s,
                                int seedval)
  {
   s.m_RDFGlobalSeed=seedval;
  }
//+------------------------------------------------------------------+
//| This function sets random decision forest construction algorithm.|
//| As for now, only one decision forest construction algorithm is   |
//| supported-a dense "baseline" RDF algorithm.                    |
//| INPUT PARAMETERS:                                                |
//|   S        -  decision forest builder object                     |
//|   AlgoType -  algorithm type:                                    |
//|               * 0 = baseline dense RDF                           |
//| OUTPUT PARAMETERS:                                               |
//|   S        -  decision forest builder, see                       |
//+------------------------------------------------------------------+
void CDForest::DFBuilderSetRDFAlgo(CDecisionForestBuilder &s,
                                   int algotype)
  {
   if(!CAp::Assert(algotype==0,__FUNCTION__": unexpected algotype"))
      return;
   s.m_RDFAlgo=algotype;
  }
//+------------------------------------------------------------------+
//| This function sets split selection algorithm used by decision    |
//| forest classifier. You may choose several algorithms, with       |
//| different speed and quality of the results.                      |
//| INPUT PARAMETERS:                                                |
//|   S              -  decision forest builder object               |
//|   SplitStrength  -  split type:                                  |
//|            * 0 = split at the random position, fastest one       |
//|            * 1 = split at the middle of the range                |
//|            * 2 = strong split at the best point of the range     |
//|                  (default)                                       |
//| OUTPUT PARAMETERS:                                               |
//|   S              -  decision forest builder, see                 |
//+------------------------------------------------------------------+
void CDForest::DFBuilderSetRDFSplitStrength(CDecisionForestBuilder &s,
                                            int splitstrength)
  {
   if(!CAp::Assert(splitstrength==0 || splitstrength==1 || splitstrength==2,__FUNCTION__": unexpected split type"))
      return;
   s.m_RDFSplitStrength=splitstrength;
  }
//+------------------------------------------------------------------+
//| This function tells decision forest construction algorithm to use|
//| Gini impurity based variable importance estimation (also known as|
//| MDI).                                                            |
//| This version of importance estimation algorithm analyzes mean    |
//| decrease in impurity (MDI) on training sample during splits. The |
//| result is divided by impurity at the root node in order to       |
//| produce estimate in [0,1] range.                                 |
//| Such estimates are fast to calculate and beautifully normalized  |
//| (sum to one) but have following downsides:                       |
//|   * They ALWAYS sum to 1.0, even if output is completely         |
//|     unpredictable. I.e. MDI allows to order variables by         |
//|     importance, but does not  tell us about "absolute"           |
//|     importances of variables                                     |
//|   * there exist some bias towards continuous and high-cardinality|
//|     categorical variables                                        |
//| NOTE: informally speaking, MDA (permutation importance) rating   |
//|       answers the question  "what  part  of  the  model          |
//|       predictive power is ruined by permuting k-th variable?"    |
//|       while MDI tells us "what part of the model predictive power|
//|       was achieved due to usage of k-th variable".               |
//| Thus, MDA rates each variable independently at "0 to 1" scale    |
//| while MDI (and OOB-MDI too) tends to divide "unit amount of      |
//| importance" between several important variables.                 |
//| If all variables are equally important, they will have same      |
//| MDI/OOB-MDI rating, equal (for OOB-MDI: roughly equal)  to       |
//| 1/NVars. However, roughly  same  picture  will  be  produced for |
//| the "all variables provide information no one is critical"       |
//| situation and for the "all variables are critical, drop any one, |
//| everything is ruined" situation.                                 |
//| Contrary to that, MDA will rate critical variable as ~1.0        |
//| important, and important but non-critical variable will have less|
//| than unit rating.                                                |
//| NOTE: quite an often MDA and MDI return same results. It         |
//|       generally happens on problems with low test set error      |
//|       (a few percents at most) and large enough training set     |
//|       to avoid overfitting.                                      |
//| The difference between MDA, MDI and OOB-MDI becomes important    |
//| only on "hard" tasks with high test set error and/or small       |
//| training set.                                                    |
//| INPUT PARAMETERS:                                                |
//|   S        -  decision forest builder object                     |
//| OUTPUT PARAMETERS:                                               |
//|   S        -  decision forest builder object. Next call to the   |
//|               forest construction function will produce:         |
//|         * importance estimates in rep.varimportances field       |
//|         * variable ranks in rep.topvars field                    |
//+------------------------------------------------------------------+
void CDForest::DFBuilderSetImportanceTrnGini(CDecisionForestBuilder &s)
  {
   s.m_RDFImportance=m_NeedTrnGini;
  }
//+------------------------------------------------------------------+
//| This function tells decision forest construction algorithm to use|
//| out-of-bag version of Gini variable importance estimation (also  |
//| known as OOB-MDI).                                               |
//| This version of importance estimation algorithm analyzes mean    |
//| decrease in impurity (MDI) on out-of-bag sample during splits.   |
//| The result is divided by impurity at the root node in order to   |
//| produce estimate in [0,1] range.                                 |
//| Such estimates are fast to calculate and resistant to overfitting|
//| issues (thanks to the out-of-bag estimates used). However, OOB   |
//| Gini rating has following downsides:                             |
//|         * there exist some bias towards continuous and           |
//|           high-cardinality categorical variables                 |
//|         * Gini rating allows us to order variables by importance,|
//|           but it is hard to define importance of the variable by |
//|           itself.                                                |
//| NOTE: informally speaking, MDA (permutation importance) rating   |
//|       answers the question "what part of the model predictive    |
//|       power is ruined by permuting k-th variable?" while MDI     |
//|       tells us "what part of the model predictive power was      |
//|       achieved due to usage of k-th variable".                   |
//| Thus, MDA rates each variable independently at "0 to 1" scale    |
//| while MDI (and OOB-MDI too) tends to divide "unit amount of      |
//| importance" between several important variables.                 |
//| If all variables are equally important, they will have same      |
//| MDI/OOB-MDI rating, equal (for OOB-MDI: roughly equal) to        |
//| 1/NVars. However, roughly same picture will be produced for the  |
//| "all variables provide information no one is critical" situation |
//| and for the "all variables are critical, drop any one, everything|
//| is ruined" situation.                                            |
//| Contrary to that, MDA will rate critical variable as ~1.0        |
//| important, and important but non-critical variable will have less|
//| than unit rating.                                                |
//| NOTE: quite an often MDA and MDI return same results. It         |
//|       generally happens on problems with low test set error      |
//|       (a few percents at most) and large enough training set to  |
//|       avoid overfitting.                                         |
//| The difference between MDA, MDI and OOB-MDI becomes important    |
//| only on "hard" tasks with high test set error and/or small       |
//| training set.                                                    |
//| INPUT PARAMETERS:                                                |
//|   S           -  decision forest builder object                  |
//| OUTPUT PARAMETERS:                                               |
//|   S           -  decision forest builder object. Next call to the|
//|                  forest construction function will produce:      |
//|            * importance estimates in rep.varimportances field    |
//|            * variable ranks in rep.topvars field                 |
//+------------------------------------------------------------------+
void CDForest::DFBuilderSetImportanceOOBGini(CDecisionForestBuilder &s)
  {
   s.m_RDFImportance=m_NeedOOBGini;
  }
//+------------------------------------------------------------------+
//| This function tells decision forest construction algorithm to use|
//| permutation variable importance estimator (also known as MDA).   |
//| This version of importance estimation algorithm analyzes mean    |
//| increase in out-of-bag sum of squared residuals after random     |
//| permutation of J-th variable. The result is divided by error     |
//| computed with all variables being perturbed in order to produce  |
//| R-squared-like estimate in [0,1] range.                          |
//| Such estimate is slower to calculate than Gini-based rating      |
//| because it needs multiple inference runs for each of variables   |
//| being studied.                                                   |
//| MDA rating has following benefits over Gini-based ones:          |
//|      * no bias towards specific variable types                   |
//|     *ability to directly evaluate "absolute" importance of some|
//|        variable at "0 to 1" scale (contrary to Gini-based rating,|
//|        which returns comparative importances).                   |
//| NOTE: informally speaking, MDA (permutation importance) rating   |
//|       answers the question "what part of the model predictive    |
//|       power is ruined by permuting k-th variable?" while MDI     |
//|       tells us "what part of the model predictive power was      |
//|       achieved due to usage of k-th variable".                   |
//| Thus, MDA rates each variable independently at "0 to 1" scale    |
//| while MDI (and OOB-MDI too) tends to divide "unit amount  of     |
//| importance" between several important variables.                 |
//| If all variables are equally important, they will have same      |
//| MDI/OOB-MDI rating, equal (for OOB-MDI: roughly equal)  to       |
//| 1/NVars. However, roughly same picture will be produced forthe   |
//| "all variables provide information no one is critical" situation |
//| and for the "all variables are critical, drop any one, everything|
//| is ruined" situation.                                            |
//| Contrary to that, MDA will rate critical variable as ~1.0        |
//| important, and important but non-critical variable will have less|
//| than unit rating.                                                |
//| NOTE: quite an often MDA and MDI return same results. It         |
//|       generally happens on problems with low test set error      |
//|       (a few percents at most) and large enough training set     |
//|       to avoid overfitting.                                      |
//| The difference between MDA, MDI and OOB-MDI becomes important    |
//| only on "hard" tasks with high test set error and/or small       |
//| training set.                                                    |
//| INPUT PARAMETERS:                                                |
//|   S           -  decision forest builder object                  |
//| OUTPUT PARAMETERS:                                               |
//|   S           -  decision forest builder object. Next call to    |
//|                  the forest construction function will produce:  |
//|            * importance estimates in rep.varimportances field    |
//|            * variable ranks in rep.topvars field                 |
//+------------------------------------------------------------------+
void CDForest::DFBuilderSetImportancePermutation(CDecisionForestBuilder &s)
  {
   s.m_RDFImportance=m_NeedPermutation;
  }
//+------------------------------------------------------------------+
//| This function tells decision forest construction algorithm to    |
//| skip variable importance estimation.                             |
//| INPUT PARAMETERS:                                                |
//|   S        -  decision forest builder object                     |
//| OUTPUT PARAMETERS:                                               |
//|   S        -  decision forest builder object. Next call to the   |
//|               forest construction function will result in forest |
//|               being built without variable importance estimation.|
//+------------------------------------------------------------------+
void CDForest::DFBuilderSetImportanceNone(CDecisionForestBuilder &s)
  {
   s.m_RDFImportance=0;
  }
//+------------------------------------------------------------------+
//| This function is an alias for dfbuilderpeekprogress(), left in   |
//| ALGLIB for backward compatibility reasons.                       |
//+------------------------------------------------------------------+
double CDForest::DFBuilderGetProgress(CDecisionForestBuilder &s)
  {
   return(DFBuilderPeekProgress(s));
  }
//+------------------------------------------------------------------+
//| This function is used to peek into decision forest construction  |
//| process from some other thread and get current progress indicator|
//| It returns value in [0,1].                                       |
//| INPUT PARAMETERS:                                                |
//|   S        -  decision forest builder object used to build forest|
//|               in some other thread                               |
//| RESULT:                                                          |
//|   progress value, in [0,1]                                       |
//+------------------------------------------------------------------+
double CDForest::DFBuilderPeekProgress(CDecisionForestBuilder &s)
  {
   double result=0;
//--- calculation
   result=s.m_RDFProgress/MathMax(s.m_RDFTotal,1);
   result=MathMax(result,0);
   result=MathMin(result,1);
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| This subroutine builds decision forest according to current      |
//| settings using dataset internally stored in the builder object.  |
//| Dense algorithm is used.                                         |
//| NOTE: this function uses dense algorithm for forest construction |
//|       independently from the dataset format (dense or sparse).   |
//| NOTE: forest built with this function is stored in-memory using  |
//|       64-bit data structures for offsets/indexes/split values. It|
//|       is possible to convert forest into more memory-efficient   |
//|       compressed binary representation. Depending on the problem |
//|       properties, 3.7x-5.7x compression factors are possible.    |
//| The downsides of compression are (a) slight reduction in  the    |
//| model accuracy and (b) ~1.5x reduction in the inference speed    |
//| (due to increased complexity of the storage format).             |
//| See comments on DFBinaryCompression() for more info.             |
//| Default settings are used by the algorithm; you can tweak them   |
//| with the help of the following functions:                        |
//|   * DFBuilderSetRFactor()    - to control a fraction of the      |
//|                                dataset used for subsampling      |
//|   * DFBuilderSetRandomVars() - to control number of variables    |
//|                                randomly chosen for decision rule |
//|                                creation                          |
//| INPUT PARAMETERS:                                                |
//|   S        -  decision forest builder object                     |
//|   NTrees   -  NTrees>=1, number of trees to train                |
//| OUTPUT PARAMETERS:                                               |
//|   D        -  decision forest. You can compress this forest to   |
//|               more compact 16-bit representation with            |
//|               DFBinaryCompression()                              |
//|   Rep      -  report, see below for information on its fields.   |
//| == report information produced by forest construction function = |
//| Decision forest training report includes following information:  |
//|      * training set errors                                       |
//|      * out-of-bag estimates of errors                            |
//|      * variable importance ratings                               |
//| Following fields are used to store information:                  |
//|   * training set errors are stored in rep.RelCLSError, rep.AvgCE,|
//|     rep.RMSError, rep.AvgError and rep.AvgRelError               |
//|   * out-of-bag estimates of errors are stored in                 |
//|     rep.oobrelclserror, rep.oobavgce, rep.oobrmserror,           |
//|     rep.oobavgerror and rep.oobavgrelerror                       |
//| Variable importance reports, if requested by                     |
//| DFBuilderSetImportanceGini(), DFBuilderSetImportanceTrnGini() or |
//| DFBuilderSetImportancePermutation() call, are stored in:         |
//|   * rep.varimportances field stores importance ratings           |
//|   * rep.topvars stores variable indexes ordered from the most    |
//|     important to less important ones                             |
//| You can find more information about report fields in:            |
//|   * comments on CDFReport structure                              |
//|   * comments on DFBuilderSetImportanceGini function              |
//|   * comments on DFBuilderSetImportanceTrnGini function           |
//|   * comments on DFBuilderDetImportancePermutation function       |
//+------------------------------------------------------------------+
void CDForest::DFBuilderBuildRandomForest(CDecisionForestBuilder &s,
                                          int ntrees,
                                          CDecisionForest &df,
                                          CDFReport &rep)
  {
//--- create variables
   int nvars=0;
   int nclasses=0;
   int npoints=0;
   int trnsize=0;
   int maxtreesize=0;
   int sessionseed=0;
   CDFVoteBuf buf;
//--- check
   if(!CAp::Assert(ntrees>=1,__FUNCTION__": ntrees<1"))
      return;

   CleanReport(s,rep);
   npoints=s.m_NPoints;
   nvars=s.m_NVars;
   nclasses=s.m_NClasses;
//--- Set up progress counter
   s.m_RDFProgress=0;
   s.m_RDFTotal=ntrees*npoints;
   if(s.m_RDFImportance==m_NeedPermutation)
      s.m_RDFTotal+=ntrees*npoints;
//--- Quick exit for empty dataset
   if(s.m_DSType==-1 || npoints==0)
     {
      if(!CAp::Assert(m_LeafNodeWdth==2,__FUNCTION__": integrity check failed"))
         return;
      df.m_ForestFormat=m_DFUncompressedV0;
      df.m_NVars=s.m_NVars;
      df.m_NClasses=s.m_NClasses;
      df.m_NTrees=1;
      df.m_BufSize=1+m_LeafNodeWdth;
      df.m_Trees.Resize(1+m_LeafNodeWdth);
      df.m_Trees.Set(0,1+m_LeafNodeWdth);
      df.m_Trees.Set(1,-1.0);
      df.m_Trees.Set(2,0.0);
      DFCreateBuffer(df,df.m_Buffer);
      return;
     }
   if(!CAp::Assert(npoints>0,__FUNCTION__": integrity check failed"))
      return;
//--- Analyze dataset statistics, perform preprocessing
   AnalyzeAndPreprocessDataset(s);
//--- Prepare "work", "vote" and "tree" pools and other settings
   trnsize=(int)MathRound(npoints*s.m_RDFRatio);
   trnsize=MathMax(trnsize,1);
   trnsize=MathMin(trnsize,npoints);
   maxtreesize=1+m_InnerNodeWidth*(trnsize-1)+m_LeafNodeWdth*trnsize;
//--- Allocation
   ResetLastError();
   if(!CAp::Assert(ArrayResize(s.m_WorkBuf,ntrees)==ntrees,StringFormat("%s: resize buffer failed (%d)",__FUNCTION__,GetLastError())))
      return;
   if(!CAp::Assert(ArrayResize(s.m_VoteBuf,ntrees)==ntrees,StringFormat("%s: resize buffer failed (%d)",__FUNCTION__,GetLastError())))
      return;
   if(!CAp::Assert(ArrayResize(s.m_TreeBuf,ntrees)==ntrees,StringFormat("%s: resize buffer failed (%d)",__FUNCTION__,GetLastError())))
      return;
   s.m_WorkBuf[0].m_varpool.Resize(nvars);
   s.m_WorkBuf[0].m_trnset.Resize(trnsize);
   s.m_WorkBuf[0].m_oobset.Resize(npoints-trnsize);
   s.m_WorkBuf[0].m_tmp0i.Resize(npoints);
   s.m_WorkBuf[0].m_tmp1i.Resize(npoints);
   s.m_WorkBuf[0].m_tmp0r.Resize(npoints);
   s.m_WorkBuf[0].m_tmp1r.Resize(npoints);
   s.m_WorkBuf[0].m_tmp2r.Resize(npoints);
   s.m_WorkBuf[0].m_tmp3r.Resize(npoints);
   s.m_WorkBuf[0].m_trnlabelsi.Resize(npoints);
   s.m_WorkBuf[0].m_trnlabelsr.Resize(npoints);
   s.m_WorkBuf[0].m_ooblabelsi.Resize(npoints);
   s.m_WorkBuf[0].m_ooblabelsr.Resize(npoints);
   s.m_WorkBuf[0].m_curvals.Resize(npoints);
   s.m_WorkBuf[0].m_bestvals.Resize(npoints);
   s.m_WorkBuf[0].m_classpriors.Resize(nclasses);
   s.m_WorkBuf[0].m_classtotals0.Resize(nclasses);
   s.m_WorkBuf[0].m_classtotals1.Resize(nclasses);
   s.m_WorkBuf[0].m_classtotals01.Resize(2*nclasses);
   s.m_WorkBuf[0].m_treebuf.Resize(maxtreesize);
   s.m_WorkBuf[0].m_trnsize=trnsize;
   s.m_WorkBuf[0].m_oobsize=npoints-trnsize;
   s.m_VoteBuf[0].m_trntotals=vector<double>::Zeros(npoints*nclasses);
   s.m_VoteBuf[0].m_oobtotals=s.m_VoteBuf[0].m_trntotals;
   s.m_VoteBuf[0].m_trncounts.Resize(npoints);
   s.m_VoteBuf[0].m_oobcounts.Resize(npoints);
   s.m_VoteBuf[0].m_trncounts.Fill(0);
   s.m_VoteBuf[0].m_oobcounts.Fill(0);
   s.m_VoteBuf[0].m_giniimportances=vector<double>::Zeros(nvars);
   for(int treeIdx=1; treeIdx<ntrees; treeIdx++)
     {
      s.m_WorkBuf[treeIdx]=s.m_WorkBuf[0];
      s.m_VoteBuf[treeIdx]=s.m_VoteBuf[0];
     }
//--- Select session seed (individual trees are constructed using
//--- combination of session and local seeds).
   sessionseed=s.m_RDFGlobalSeed;
   if(s.m_RDFGlobalSeed<=0)
      sessionseed=CMath::RandomInteger(30000);
//--- Prepare In-and-Out-of-Bag matrix, if needed
   s.m_NeedIOBMatrix=(s.m_RDFImportance==m_NeedPermutation);
   if(s.m_NeedIOBMatrix)
     {
      //--- Prepare default state of In-and-Out-of-Bag matrix
      s.m_IOBMatrix.Resize(ntrees,npoints);
      s.m_IOBMatrix.Fill((int)false);
     }
//--- Build tree
   BuildRandomTree(s,0,ntrees);
//--- Merge tree and output result
   MergeTrees(s,df);
//--- Process voting results and output training set and OOB errors.
//--- Finalize tree construction.
   ProcessVotingResults(s,ntrees,buf,rep);
   DFCreateBuffer(df,df.m_Buffer);
//--- Perform variable importance estimation
   EstimateVariableImportance(s,sessionseed,df,ntrees,rep);
//--- Update progress counter
   s.m_RDFProgress=s.m_RDFTotal;
  }
//+------------------------------------------------------------------+
//| This function performs binary compression of the decision forest.|
//| Original decision forest produced by the forest builder is stored|
//| using 64-bit representation for all numbers - offsets, variable  |
//| indexes, split points.                                           |
//| It is possible to significantly reduce model size by means of:   |
//|   * using compressed dynamic encoding for integers (offsets and  |
//|     variable indexes), which uses just 1 byte to store small ints|
//|     (less than 128), just 2 bytes for larger values (less than   |
//|     128^2) and so on                                             |
//|   * storing floating point numbers using 8-bit exponent and      |
//|     16-bit mantissa                                              |
//| As result, model needs significantly less memory (compression    |
//| factor depends on  variable and class counts). In particular:    |
//|   * NVars<128 and NClasses<128 result in 4.4x-5.7x model size    |
//|     reduction                                                    |
//|   * NVars<16384 and NClasses<128 result in 3.7x-4.5x model size  |
//|     reduction                                                    |
//| Such storage format performs lossless compression of all integers|
//| but compression of floating point values (split values) is lossy,|
//| with roughly 0.01% relative error introduced during rounding.    |
//| Thus, we recommend you to re-evaluate model accuracy after       |
//| compression.                                                     |
//| Another downside of compression is ~1.5x reduction in the        |
//| inference speed due to necessity of dynamic decompression of the |
//| compressed model.                                                |
//| INPUT PARAMETERS:                                                |
//|   DF       -  decision forest built by forest builder            |
//| OUTPUT PARAMETERS:                                               |
//|   DF       -  replaced by compressed forest                      |
//| RESULT:                                                          |
//| compression factor (in-RAM size of the compressed model vs than  |
//| of the uncompressed one), positive number larger than 1.0        |
//+------------------------------------------------------------------+
double CDForest::DFBinaryCompression(CDecisionForest &df)
  {
   return(BinaryCompression(df,false));
  }
//+------------------------------------------------------------------+
//| This is a 8-bit version of DFBinaryCompression.                  |
//| Not recommended for external use because it is too lossy.        |
//+------------------------------------------------------------------+
double CDForest::DFBinaryCompression8(CDecisionForest &df)
  {
   return(BinaryCompression(df,true));
  }
//+------------------------------------------------------------------+
//| Procesing                                                        |
//| INPUT PARAMETERS:                                                |
//|     DF      -   decision forest model                            |
//|     X       -   input vector,  array[0..NVars-1].                |
//| OUTPUT PARAMETERS:                                               |
//|     Y       -   result. Regression estimate when solving         |
//|                 regression task, vector of posterior             |
//|                 probabilities for classification task.           |
//| See also DFProcessI.                                             |
//+------------------------------------------------------------------+
void CDForest::DFProcess(CDecisionForest &df,double &x[],double &y[])
  {
   CRowDouble X=x;
   CRowDouble Y;
   DFProcess(df,X,Y);
   Y.ToArray(y);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CDForest::DFProcess(CDecisionForest &df,CRowDouble &x,CRowDouble &y)
  {
//--- create variables
   int    offs=0;
   int    i=0;
   double v=0;
   int    treesize=0;
   bool   processed=false;
//--- Proceed
//--- Although comments above warn you about thread-unsafety of this
//--- function, it is de facto thread-safe. However, thread safety is
//--- an accidental side-effect of the specific inference algorithm
//--- being used. It may disappear in the future versions of the DF
//--- models, so you should NOT rely on it.
   y.Resize(df.m_NClasses);
//--- initialization
   y.Fill(0);
//--- calculation
   if(df.m_ForestFormat==m_DFUncompressedV0)
     {
      //--- Process trees stored in uncompressed format
      for(i=0; i<df.m_NTrees; i++)
        {
         DFProcessInternalUncompressed(df,offs,offs+1,x,y);
         offs+=(int)MathRound(df.m_Trees[offs]);
        }
      processed=true;
     }
   if(df.m_ForestFormat==m_DFCompressedV0)
     {
      //--- Process trees stored in compressed format
      offs=0;
      for(i=0; i<df.m_NTrees; i++)
        {
         treesize=UnstreamUint(df.m_Trees8,offs);
         DFProcessInternalCompressed(df,offs,x,y);
         offs+=treesize;
        }
      processed=true;
     }
   if(!CAp::Assert(processed,__FUNCTION__": integrity check failed (unexpected format?)"))
      return;
   y*=1.0/(double)df.m_NTrees;
  }
//+------------------------------------------------------------------+
//| 'interactive' variant of DFProcess for languages like Python     |
//| which support constructs like "Y = DFProcessI(DF,X)" and         |
//| interactive mode of interpreter                                  |
//| This function allocates new array on each call, so it is         |
//| significantly slower than its 'non-interactive' counterpart, but |
//| it is more convenient when you call it from command line.        |
//+------------------------------------------------------------------+
void CDForest::DFProcessI(CDecisionForest &df,double &x[],double &y[])
  {
   DFProcess(df,x,y);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CDForest::DFProcessI(CDecisionForest &df,CRowDouble &x,CRowDouble &y)
  {
   DFProcess(df,x,y);
  }
//+------------------------------------------------------------------+
//|  This function returns first component of the inferred vector    |
//| (i.e. one with index #0).                                        |
//| It is a convenience wrapper for dfprocess() intended for either: |
//|      * 1-dimensional regression problems                         |
//|      * 2-class classification problems                           |
//| In the former case this function returns inference result as     |
//| scalar, which is definitely more convenient that wrapping it as  |
//| vector. In the latter case it returns probability of object      |
//| belonging to class #0.                                           |
//| If you call it for anything different from two cases above, it   |
//| will work as defined, i.e. return y[0], although it is of less   |
//| use in such cases.                                               |
//| INPUT PARAMETERS:                                                |
//|   Model    -  DF model                                           |
//|   X        -  input vector,  array[0..NVars-1].                  |
//| RESULT:                                                          |
//|   Y[0]                                                           |
//+------------------------------------------------------------------+
double CDForest::DFProcess0(CDecisionForest &model,CRowDouble &x)
  {
//--- copy
   model.m_Buffer.m_x=x;
//--- function call
   DFProcess(model,model.m_Buffer.m_x,model.m_Buffer.m_y);
//--- return result
   return(model.m_Buffer.m_y[0]);
  }
//+------------------------------------------------------------------+
//| This function returns most probable class number for an input X. |
//| It is same as calling DFProcess(model,x,y), then determining     |
//| i=ArgMax(y[i]) and returning i.                                  |
//| A class number in [0,NOut) range in returned for classification  |
//| problems, -1 is returned when this function is called for        |
//| regression problems.                                             |
//| INPUT PARAMETERS:                                                |
//|   Model    -  decision forest model                              |
//|   X        -  input vector,  array[0..NVars-1].                  |
//| RESULT:                                                          |
//|   class number, -1 for regression tasks                          |
//+------------------------------------------------------------------+
int CDForest::DFClassify(CDecisionForest &model,CRowDouble &x)
  {
//--- check
   if(model.m_NClasses<2)
      return(-1);
   model.m_Buffer.m_x=x;
   DFProcess(model,model.m_Buffer.m_x,model.m_Buffer.m_y);
//--- return result
   return (int)model.m_Buffer.m_y.ArgMax();
  }
//+------------------------------------------------------------------+
//| Relative classification error on the test set                    |
//| INPUT PARAMETERS:                                                |
//|     DF      -   decision forest model                            |
//|     XY      -   test set                                         |
//|     NPoints -   test set size                                    |
//| RESULT:                                                          |
//|     percent of incorrectly classified cases.                     |
//|     Zero if model solves regression task.                        |
//+------------------------------------------------------------------+
double CDForest::DFRelClsError(CDecisionForest &df,CMatrixDouble &xy,
                               const int npoints)
  {
   return((double)DFClsError(df,xy,npoints)/(double)npoints);
  }
//+------------------------------------------------------------------+
//| Average cross-entropy (in bits per element) on the test set      |
//| INPUT PARAMETERS:                                                |
//|     DF      -   decision forest model                            |
//|     XY      -   test set                                         |
//|     NPoints -   test set size                                    |
//| RESULT:                                                          |
//|     CrossEntropy/(NPoints*LN(2)).                                |
//|     Zero if model solves regression task.                        |
//+------------------------------------------------------------------+
double CDForest::DFAvgCE(CDecisionForest &df,CMatrixDouble &xy,
                         const int npoints)
  {
//--- create variables
   double result=0;
   CRowDouble x;
   CRowDouble y;

   for(int i=0; i<npoints; i++)
     {
      x=xy[i]+0;
      x.Resize(df.m_NVars);
      //--- function call
      DFProcess(df,x,y);
      //--- check
      if(df.m_NClasses>1)
        {
         //--- classification-specific code
         int k=(int)MathRound(xy.Get(i,df.m_NVars));
         //--- check
         if(y[k]!=0.0)
            result-=MathLog(y[k]);
         else
            result-=MathLog(CMath::m_minrealnumber);
        }
     }
//--- return result
   return(result/npoints);
  }
//+------------------------------------------------------------------+
//| RMS error on the test set                                        |
//| INPUT PARAMETERS:                                                |
//|     DF      -   decision forest model                            |
//|     XY      -   test set                                         |
//|     NPoints -   test set size                                    |
//| RESULT:                                                          |
//|     root mean square error.                                      |
//|     Its meaning for regression task is obvious. As for           |
//|     classification task,RMS error means error when estimating    |
//|     posterior probabilities.                                     |
//+------------------------------------------------------------------+
double CDForest::DFRMSError(CDecisionForest &df,CMatrixDouble &xy,
                            const int npoints)
  {
//--- create variables
   double result=0;
   CRowDouble x;
   CRowDouble y;

   for(int i=0; i<npoints; i++)
     {
      x=xy[i]+0;
      x.Resize(df.m_NVars);
      //--- function call
      DFProcess(df,x,y);
      //--- check
      if(df.m_NClasses>1)
        {
         //--- classification-specific code
         int k=(int)MathRound(xy.Get(i,df.m_NVars));
         y.Add(k,-1);
         result+=MathPow(y.ToVector()+0,2.0).Sum();
        }
      else
        {
         //--- regression-specific code
         result+=CMath::Sqr(y[0]-xy.Get(i,df.m_NVars));
        }
     }
//--- return result
   return(MathSqrt(result/(npoints*df.m_NClasses)));
  }
//+------------------------------------------------------------------+
//| Average error on the test set                                    |
//| INPUT PARAMETERS:                                                |
//|     DF      -   decision forest model                            |
//|     XY      -   test set                                         |
//|     NPoints -   test set size                                    |
//| RESULT:                                                          |
//|     Its meaning for regression task is obvious. As for           |
//|     classification task, it means average error when estimating  |
//|     posterior probabilities.                                     |
//+------------------------------------------------------------------+
double CDForest::DFAvgError(CDecisionForest &df,CMatrixDouble &xy,
                            const int npoints)
  {
//--- create variables
   double result=0;
   CRowDouble x;
   CRowDouble y;

   for(int i=0; i<npoints; i++)
     {
      //--- copy
      x=xy[i]+0;
      x.Resize(df.m_NVars);
      //--- function call
      DFProcess(df,x,y);
      //--- check
      if(df.m_NClasses>1)
        {
         //--- classification-specific code
         int k=(int)MathRound(xy.Get(i,df.m_NVars));
         y.Add(k,-1);
         result+=(y.Abs()+0).Sum();
        }
      else
        {
         //--- regression-specific code
         result+=MathAbs(y[0]-xy.Get(i,df.m_NVars));
        }
     }
//--- return result
   return(result/(npoints*df.m_NClasses));
  }
//+------------------------------------------------------------------+
//| Average relative error on the test set                           |
//| INPUT PARAMETERS:                                                |
//|     DF      -   decision forest model                            |
//|     XY      -   test set                                         |
//|     NPoints -   test set size                                    |
//| RESULT:                                                          |
//|     Its meaning for regression task is obvious. As for           |
//|     classification task, it means average relative error when    |
//|     estimating posterior probability of belonging to the correct |
//|     class.                                                       |
//+------------------------------------------------------------------+
double CDForest::DFAvgRelError(CDecisionForest &df,CMatrixDouble &xy,
                               const int npoints)
  {
//--- create variables
   double result=0;
   int    relcnt=0;
//--- creating arrays
   CRowDouble x;
   CRowDouble y;
//--- initialization
   for(int i=0; i<npoints; i++)
     {
      //--- copy
      x=xy[i]+0;
      x.Resize(df.m_NVars);
      //--- function call
      DFProcess(df,x,y);
      //--- check
      if(df.m_NClasses>1)
        {
         //--- classification-specific code
         int k=(int)MathRound(xy.Get(i,df.m_NVars));
         if(k<0 || k>=df.m_NVars)
            continue;
         result+=MathAbs(y[k]-1);
         relcnt++;
        }
      else
        {
         //--- regression-specific code
         if(xy.Get(i,df.m_NVars)!=0.0)
           {
            result+=MathAbs((y[0]-xy.Get(i,df.m_NVars))/xy.Get(i,df.m_NVars));
            relcnt ++;
           }
        }
     }
//--- check
   if(relcnt>0)
      result=result/relcnt;
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| Copying of DecisionForest strucure                               |
//| INPUT PARAMETERS:                                                |
//|     DF1 -   original                                             |
//| OUTPUT PARAMETERS:                                               |
//|     DF2 -   copy                                                 |
//+------------------------------------------------------------------+
void CDForest::DFCopy(CDecisionForest &df1,CDecisionForest &df2)
  {
   if(df1.m_ForestFormat==m_DFUncompressedV0)
     {
      df2.m_ForestFormat=df1.m_ForestFormat;
      df2.m_NVars=df1.m_NVars;
      df2.m_NClasses=df1.m_NClasses;
      df2.m_NTrees=df1.m_NTrees;
      df2.m_BufSize=df1.m_BufSize ;
      df2.m_Trees=df1.m_Trees;
      DFCreateBuffer(df2,df2.m_Buffer);
      return;
     }
   if(df1.m_ForestFormat==m_DFCompressedV0)
     {
      df2.m_ForestFormat=df1.m_ForestFormat;
      df2.m_NVars=df1.m_NVars;
      df2.m_NClasses=df1.m_NClasses;
      df2.m_NTrees=df1.m_NTrees;
      df2.m_BufSize=df1.m_BufSize ;
      df2.m_UseMantissa8=df1.m_UseMantissa8;
      df2.m_Trees8=df1.m_Trees8;
      DFCreateBuffer(df2,df2.m_Buffer);
      return;
     }
   CAp::Assert(false,__FUNCTION__": unexpected forest format");
  }
//+------------------------------------------------------------------+
//| Serializer: allocation                                           |
//+------------------------------------------------------------------+
void CDForest::DFAlloc(CSerializer &s,CDecisionForest &forest)
  {
//--- preparation to serialize
   if(forest.m_ForestFormat==m_DFUncompressedV0)
     {
      s.Alloc_Entry();
      s.Alloc_Entry();
      s.Alloc_Entry();
      s.Alloc_Entry();
      s.Alloc_Entry();
      s.Alloc_Entry();
      CApServ::AllocRealArray(s,forest.m_Trees,forest.m_BufSize);
      return;
     }
   if(forest.m_ForestFormat==m_DFCompressedV0)
     {
      s.Alloc_Entry();
      s.Alloc_Entry();
      s.Alloc_Entry();
      s.Alloc_Entry();
      s.Alloc_Entry();
      s.Alloc_Entry();
      CApServ::AllocIntegerArray(s,forest.m_Trees8,forest.m_BufSize);
      return;
     }
   CAp::Assert(false,__FUNCTION__": unexpected forest format");
  }
//+------------------------------------------------------------------+
//| Serializer: serialization                                        |
//+------------------------------------------------------------------+
void CDForest::DFSerialize(CSerializer &s,CDecisionForest &forest)
  {
//--- serializetion
   if(forest.m_ForestFormat==m_DFUncompressedV0)
     {
      s.Serialize_Int(CSCodes::GetRDFSerializationCode());
      s.Serialize_Int(m_DFUncompressedV0);
      s.Serialize_Int(forest.m_NVars);
      s.Serialize_Int(forest.m_NClasses);
      s.Serialize_Int(forest.m_NTrees);
      s.Serialize_Int(forest.m_BufSize);
      CApServ::SerializeRealArray(s,forest.m_Trees,forest.m_BufSize);
      return;
     }
   if(forest.m_ForestFormat==m_DFCompressedV0)
     {
      s.Serialize_Int(CSCodes::GetRDFSerializationCode());
      s.Serialize_Int(forest.m_ForestFormat);
      s.Serialize_Bool(forest.m_UseMantissa8);
      s.Serialize_Int(forest.m_NVars);
      s.Serialize_Int(forest.m_NClasses);
      s.Serialize_Int(forest.m_NTrees);
      CApServ::SerializeIntegerArray(s,forest.m_Trees8,forest.m_Trees8.Size());
      return;
     }
   CAp::Assert(false,__FUNCTION__": unexpected forest format");
  }
//+------------------------------------------------------------------+
//| Serializer: unserialization                                      |
//+------------------------------------------------------------------+
void CDForest::DFUnserialize(CSerializer &s,CDecisionForest &forest)
  {
   bool processed=false;
//--- check correctness of header
   int i0=s.Unserialize_Int();
   if(!CAp::Assert(i0==CSCodes::GetRDFSerializationCode(),__FUNCTION__": stream header corrupted"))
      return;
//--- Read forest
   int forestformat=s.Unserialize_Int();
   if(forestformat==m_DFUncompressedV0)
     {
      //--- Unserialize data
      forest.m_ForestFormat=forestformat;
      forest.m_NVars=s.Unserialize_Int();
      forest.m_NClasses=s.Unserialize_Int();
      forest.m_NTrees=s.Unserialize_Int();
      forest.m_BufSize=s.Unserialize_Int();
      CApServ::UnserializeRealArray(s,forest.m_Trees);
      processed=true;
     }
   if(forestformat==m_DFCompressedV0)
     {
      //--- Unserialize data
      forest.m_ForestFormat=forestformat;
      forest.m_UseMantissa8=s.Unserialize_Bool();
      forest.m_NVars=s.Unserialize_Int();
      forest.m_NClasses=s.Unserialize_Int();
      forest.m_NTrees=s.Unserialize_Int();
      CApServ::UnserializeIntegerArray(s,forest.m_Trees8);
      processed=true;
     }
   if(!CAp::Assert(processed,__FUNCTION__": unexpected forest format"))
      return;
//--- Prepare buffer
   DFCreateBuffer(forest,forest.m_Buffer);
  }
//+------------------------------------------------------------------+
//| This subroutine builds random decision forest.                   |
//| ---- DEPRECATED VERSION! USE DECISION FOREST BUILDER OBJECT ---- |
//+------------------------------------------------------------------+
void CDForest::DFBuildRandomDecisionForest(CMatrixDouble &xy,
                                           int npoints,
                                           int nvars,
                                           int nclasses,
                                           int ntrees,
                                           double r,
                                           int &info,
                                           CDecisionForest &df,
                                           CDFReport &rep)
  {
   info=0;
//--- check
   if(r<=0.0 || r>1.0)
     {
      info=-1;
      return;
     }

   int samplesize=MathMax((int)MathRound(r*npoints),1);
   DFBuildInternal(xy,npoints,nvars,nclasses,ntrees,samplesize,MathMax(nvars/2,1),m_DFUseStrongSplits+m_DFUseEVS,info,df,rep);
  }


//+------------------------------------------------------------------+
//| This subroutine builds random decision forest.                   |
//| ---- DEPRECATED VERSION! USE DECISION FOREST BUILDER OBJECT ---- |
//+------------------------------------------------------------------+
void CDForest::DFBuildRandomDecisionForestX1(CMatrixDouble &xy,
                                             int npoints,
                                             int nvars,
                                             int nclasses,
                                             int ntrees,
                                             int nrndvars,
                                             double r,
                                             int &info,
                                             CDecisionForest &df,
                                             CDFReport &rep)
  {
   info=0;
//--- check
   if(r<=0.0 || r>1.0)
     {
      info=-1;
      return;
     }
   if(nrndvars<=0 || nrndvars>nvars)
     {
      info=-1;
      return;
     }

   int samplesize=MathMax((int)MathRound(r*npoints),1);
   DFBuildInternal(xy,npoints,nvars,nclasses,ntrees,samplesize,nrndvars,m_DFUseStrongSplits+m_DFUseEVS,info,df,rep);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CDForest::DFBuildInternal(CMatrixDouble &xy,
                               int npoints,
                               int nvars,
                               int nclasses,
                               int ntrees,
                               int samplesize,
                               int nfeatures,
                               int flags,
                               int &info,
                               CDecisionForest &df,
                               CDFReport &rep)
  {
   CDecisionForestBuilder builder;
   info=0;
//--- Test for inputs
   if(npoints<1 || samplesize<1 || samplesize>npoints || nvars<1 || nclasses<1 || ntrees<1 || nfeatures<1)
     {
      info=-1;
      return;
     }
   if(nclasses>1)
      for(int i=0; i<=npoints-1; i++)
         if(MathRound(xy.Get(i,nvars))<0 || MathRound(xy.Get(i,nvars))>=nclasses)
           {
            info=-2;
            return;
           }

   info=1;
   DFBuilderCreate(builder);
   DFBuilderSetDataset(builder,xy,npoints,nvars,nclasses);
   DFBuilderSetSubsampleRatio(builder,(double)samplesize/(double)npoints);
   DFBuilderSetRndVars(builder,nfeatures);
   DFBuilderBuildRandomForest(builder,ntrees,df,rep);
  }
//+------------------------------------------------------------------+
//| Builds a range of random trees [TreeIdx0, TreeIdx1) using        |
//| decision forest algorithm. Tree index is used to seed per - tree |
//| RNG.                                                             |
//+------------------------------------------------------------------+
void CDForest::BuildRandomTree(CDecisionForestBuilder &s,int treeidx0,
                               int treeidx1)
  {
//--- create variables
   int i=0;
   int j=0;
   int npoints=0;
   int nvars=0;
   int nclasses=0;
   CHighQualityRandState rs;
   int treesize=0;
   int varstoselect=0;
   int workingsetsize=0;
   double meanloss=0;
//--- Prepare
   npoints=s.m_NPoints;
   nvars=s.m_NVars;
   nclasses=s.m_NClasses;
//---
   if(treeidx0>treeidx1)
      CApServ::Swap(treeidx0,treeidx1);
   if(treeidx0==treeidx1)
      treeidx1++;
   for(int treeidx=treeidx0; treeidx<treeidx1; treeidx++)
     {
      if(s.m_RDFGlobalSeed>0)
        {
         CHighQualityRand::HQRndSeed(s.m_RDFGlobalSeed,1+treeidx,rs);
        }
      else
        {
         CHighQualityRand::HQRndSeed(CMath::RandomInteger(30000),1+treeidx,rs);
        }
      //--- Prepare everything for tree construction.
      if(!CAp::Assert(s.m_WorkBuf[treeidx].m_trnsize>=1,__FUNCTION__": integrity check failed (34636)"))
         return;
      if(!CAp::Assert(s.m_WorkBuf[treeidx].m_oobsize>=0,__FUNCTION__": integrity check failed (45745)"))
         return;
      if(!CAp::Assert(s.m_WorkBuf[treeidx].m_trnsize+s.m_WorkBuf[treeidx].m_oobsize==npoints,__FUNCTION__": integrity check failed (89415)"))
         return;
      workingsetsize=-1;
      s.m_WorkBuf[treeidx].m_varpoolsize=0;
      for(i=0; i<nvars; i++)
         if(s.m_DSMin[i]!=s.m_DSMax[i])
           {
            s.m_WorkBuf[treeidx].m_varpool.Set(s.m_WorkBuf[treeidx].m_varpoolsize,i);
            s.m_WorkBuf[treeidx].m_varpoolsize++;
           }
      workingsetsize=s.m_WorkBuf[treeidx].m_varpoolsize;
      if(!CAp::Assert(workingsetsize>=0,__FUNCTION__": integrity check failed (73f5)"))
         return;
      for(i=0; i<npoints; i++)
         s.m_WorkBuf[treeidx].m_tmp0i.Set(i,i);
      for(i=0; i<s.m_WorkBuf[treeidx].m_trnsize; i++)
        {
         j=CHighQualityRand::HQRndUniformI(rs,npoints-i);
         s.m_WorkBuf[treeidx].m_tmp0i.Swap(i,i+j);
         s.m_WorkBuf[treeidx].m_trnset.Set(i,s.m_WorkBuf[treeidx].m_tmp0i[i]);
         if(nclasses>1)
            s.m_WorkBuf[treeidx].m_trnlabelsi.Set(i,s.m_DSIVal[s.m_WorkBuf[treeidx].m_tmp0i[i]]);
         else
            s.m_WorkBuf[treeidx].m_trnlabelsr.Set(i,s.m_DSRVal[s.m_WorkBuf[treeidx].m_tmp0i[i]]);
         if(s.m_NeedIOBMatrix)
            s.m_IOBMatrix.Set(treeidx,s.m_WorkBuf[treeidx].m_trnset[i],(int)true);
        }
      for(i=0; i<s.m_WorkBuf[treeidx].m_oobsize; i++)
        {
         j=s.m_WorkBuf[treeidx].m_tmp0i[s.m_WorkBuf[treeidx].m_trnsize+i];
         s.m_WorkBuf[treeidx].m_oobset.Set(i,j);
         if(nclasses>1)
            s.m_WorkBuf[treeidx].m_ooblabelsi.Set(i,s.m_DSIVal[j]);
         else
            s.m_WorkBuf[treeidx].m_ooblabelsr.Set(i,s.m_DSRVal[j]);
        }
      varstoselect=(int)MathRound(MathSqrt(nvars));
      if(s.m_RDFVars>0.0)
         varstoselect=(int)MathRound(s.m_RDFVars);
      else
         if(s.m_RDFVars<0.0)
           {
            varstoselect=(int)MathRound(-(nvars*s.m_RDFVars));
           }
      varstoselect=MathMax(varstoselect,1);
      varstoselect=MathMin(varstoselect,nvars);
      //--- Perform recurrent construction
      if(s.m_RDFImportance==m_NeedTrnGini)
         meanloss=MeanNRMS2(nclasses,s.m_WorkBuf[treeidx].m_trnlabelsi,s.m_WorkBuf[treeidx].m_trnlabelsr,0,s.m_WorkBuf[treeidx].m_trnsize,s.m_WorkBuf[treeidx].m_trnlabelsi,s.m_WorkBuf[treeidx].m_trnlabelsr,0,s.m_WorkBuf[treeidx].m_trnsize,s.m_WorkBuf[treeidx].m_tmpnrms2);
      else
         meanloss=MeanNRMS2(nclasses,s.m_WorkBuf[treeidx].m_trnlabelsi,s.m_WorkBuf[treeidx].m_trnlabelsr,0,s.m_WorkBuf[treeidx].m_trnsize,s.m_WorkBuf[treeidx].m_ooblabelsi,s.m_WorkBuf[treeidx].m_ooblabelsr,0,s.m_WorkBuf[treeidx].m_oobsize,s.m_WorkBuf[treeidx].m_tmpnrms2);
      treesize=1;
      BuildRandomTreeRec(s,s.m_WorkBuf[treeidx],workingsetsize,varstoselect,s.m_WorkBuf[treeidx].m_treebuf,s.m_VoteBuf[treeidx],rs,0,s.m_WorkBuf[treeidx].m_trnsize,0,s.m_WorkBuf[treeidx].m_oobsize,meanloss,meanloss,treesize);
      s.m_WorkBuf[treeidx].m_treebuf.Set(0,treesize);
      //--- Store tree
      s.m_TreeBuf[treeidx].m_treebuf=s.m_WorkBuf[treeidx].m_treebuf;
      s.m_TreeBuf[treeidx].m_treeidx=treeidx;
     }
  }
//+------------------------------------------------------------------+
//| Recurrent tree construction function using caller - allocated    |
//| buffers and caller - initialized RNG.                            |
//| Following iterms are processed:                                  |
//|   * items [Idx0, Idx1) of WorkBuf.TrnSet                         |
//|   * items [OOBIdx0, OOBIdx1) of WorkBuf.OOBSet                   |
//| TreeSize on input must be 1(header element of the tree), on      |
//| output it contains size of the tree.                             |
//| OOBLoss on input must contain value of MeanNRMS2(...) computed   |
//| for entire dataset.                                              |
//| Variables from #0 to #WorkingSet - 1 from WorkBuf.VarPool are    |
//| used(for block algorithm: blocks, not vars)                      |
//+------------------------------------------------------------------+
void CDForest::BuildRandomTreeRec(CDecisionForestBuilder &s,
                                  CDFWorkBuf &workbuf,
                                  int workingset,
                                  int varstoselect,
                                  CRowDouble &treebuf,
                                  CDFVoteBuf &votebuf,
                                  CHighQualityRandState &rs,
                                  int idx0,
                                  int idx1,
                                  int oobidx0,
                                  int oobidx1,
                                  double meanloss,
                                  double topmostmeanloss,
                                  int &treesize)
  {
//---create variables
   int    npoints=0;
   int    nclasses=0;
   int    i=0;
   int    j=0;
   int    j0=0;
   double v=0;
   bool   labelsaresame;
   int    offs=0;
   int    varbest=0;
   double splitbest=0;
   int    i1=0;
   int    i2=0;
   int    idxtrn=0;
   int    idxoob=0;
   double meanloss0=0;
   double meanloss1=0;
//--- check
   if(!CAp::Assert(s.m_DSType==0,__FUNCTION__": not supported skbdgfsi!"))
      return;
   if(!CAp::Assert(idx0<idx1,__FUNCTION__": integrity check failed (3445)"))
      return;
   if(!CAp::Assert(oobidx0<=oobidx1,__FUNCTION__": integrity check failed (7452)"))
      return;

   npoints=s.m_NPoints;
   nclasses=s.m_NClasses;
//--- Check labels: all same or not?
   if(nclasses>1)
     {
      labelsaresame=true;
      workbuf.m_classpriors.Fill(0);
      j0=workbuf.m_trnlabelsi[idx0];
      for(i=idx0; i<idx1; i++)
        {
         j=workbuf.m_trnlabelsi[i];
         workbuf.m_classpriors.Add(j,1);
         labelsaresame=labelsaresame && j0==j;
        }
     }
   else
      labelsaresame=false;
//--- Leaf node
   if(idx1-idx0==1 || labelsaresame)
     {
      if(nclasses==1)
         OutputLeaf(s,workbuf,treebuf,votebuf,idx0,idx1,oobidx0,oobidx1,treesize,workbuf.m_trnlabelsr[idx0]);
      else
         OutputLeaf(s,workbuf,treebuf,votebuf,idx0,idx1,oobidx0,oobidx1,treesize,workbuf.m_trnlabelsi[idx0]);
      return;
     }
//--- Non-leaf node.
//--- Investigate possible splits.
   if(!CAp::Assert(s.m_RDFAlgo==0,__FUNCTION__": unexpected algo"))
      return;
   ChooseCurrentSplitDense(s,workbuf,workingset,varstoselect,rs,idx0,idx1,varbest,splitbest);
   if(varbest<0)
     {
      //--- No good split was found; make leaf (label is randomly chosen) and exit.
      if(nclasses>1)
         v=workbuf.m_trnlabelsi[idx0+CHighQualityRand::HQRndUniformI(rs,idx1-idx0)];
      else
         v=workbuf.m_trnlabelsr[idx0+CHighQualityRand::HQRndUniformI(rs,idx1-idx0)];
      OutputLeaf(s,workbuf,treebuf,votebuf,idx0,idx1,oobidx0,oobidx1,treesize,v);
      return;
     }
//--- Good split WAS found, we can perform it:
//--- * first, we split training set
//--- * then, we similarly split OOB set
   if(!CAp::Assert(s.m_DSType==0,__FUNCTION__": not supported 54bfdh"))
      return;
   offs=npoints*varbest;
   i1=idx0;
   i2=idx1-1;
   while(i1<=i2)
     {
      //--- Reorder indexes so that left partition is in [Idx0..I1),
      //--- and right partition is in [I2+1..Idx1)
      if(workbuf.m_bestvals[i1]<splitbest)
        {
         i1 ++;
         continue;
        }
      if(workbuf.m_bestvals[i2]>=splitbest)
        {
         i2--;
         continue;
        }
      workbuf.m_trnset.Swap(i1,i2);
      if(nclasses>1)
         workbuf.m_trnlabelsi.Swap(i1,i2);
      else
         workbuf.m_trnlabelsr.Swap(i1,i2);
      i1++;
      i2--;
     }
   if(!CAp::Assert(i1==i2+1,__FUNCTION__": integrity check failed (45rds3)"))
      return;
   idxtrn=i1;
   if(oobidx0<oobidx1)
     {
      //--- Unlike the training subset, the out-of-bag subset corresponding to the
      //--- current sequence of decisions can be empty; thus, we have to explicitly
      //--- handle situation of zero OOB subset.
      i1=oobidx0;
      i2=oobidx1-1;
      while(i1<=i2)
        {
         //--- Reorder indexes so that left partition is in [Idx0..I1),
         //--- and right partition is in [I2+1..Idx1)
         if(s.m_DSData[offs+workbuf.m_oobset[i1]]<splitbest)
           {
            i1++;
            continue;
           }
         if(s.m_DSData[offs+workbuf.m_oobset[i2]]>=splitbest)
           {
            i2--;
            continue;
           }
         workbuf.m_oobset.Swap(i1,i2);
         if(nclasses>1)
            workbuf.m_ooblabelsi.Swap(i1,i2);
         else
            workbuf.m_ooblabelsr.Swap(i1,i2);
         i1++;
         i2--;
        }
      if(!CAp::Assert(i1==i2+1,__FUNCTION__": integrity check failed (643fs3)"))
         return;
      idxoob=i1;
     }
   else
      idxoob=oobidx0;
//--- Compute estimates of NRMS2 loss over TRN or OOB subsets, update Gini importances
   if(s.m_RDFImportance==m_NeedTrnGini)
     {
      meanloss0=MeanNRMS2(nclasses,workbuf.m_trnlabelsi,workbuf.m_trnlabelsr,idx0,idxtrn,workbuf.m_trnlabelsi,workbuf.m_trnlabelsr,idx0,idxtrn,workbuf.m_tmpnrms2);
      meanloss1=MeanNRMS2(nclasses,workbuf.m_trnlabelsi,workbuf.m_trnlabelsr,idxtrn,idx1,workbuf.m_trnlabelsi,workbuf.m_trnlabelsr,idxtrn,idx1,workbuf.m_tmpnrms2);
     }
   else
     {
      meanloss0=MeanNRMS2(nclasses,workbuf.m_trnlabelsi,workbuf.m_trnlabelsr,idx0,idxtrn,workbuf.m_ooblabelsi,workbuf.m_ooblabelsr,oobidx0,idxoob,workbuf.m_tmpnrms2);
      meanloss1=MeanNRMS2(nclasses,workbuf.m_trnlabelsi,workbuf.m_trnlabelsr,idxtrn,idx1,workbuf.m_ooblabelsi,workbuf.m_ooblabelsr,idxoob,oobidx1,workbuf.m_tmpnrms2);
     }
   votebuf.m_giniimportances.Add(varbest,(meanloss-(meanloss0+meanloss1))/(topmostmeanloss+1.0e-20));
//--- Generate tree node and subtrees (recursively)
   treebuf.Set(treesize,varbest);
   treebuf.Set(treesize+1,splitbest);
   i=treesize;
   treesize=treesize+m_InnerNodeWidth;
   BuildRandomTreeRec(s,workbuf,workingset,varstoselect,treebuf,votebuf,rs,idx0,idxtrn,oobidx0,idxoob,meanloss0,topmostmeanloss,treesize);
   treebuf.Set(i+2,treesize);
   BuildRandomTreeRec(s,workbuf,workingset,varstoselect,treebuf,votebuf,rs,idxtrn,idx1,idxoob,oobidx1,meanloss1,topmostmeanloss,treesize);
  }
//+------------------------------------------------------------------+
//| Estimates permutation variable importance ratings for a range of |
//| dataset points.                                                  |
//| Initial call to this function should span entire range of the    |
//| dataset, [Idx0, Idx1) = [0, NPoints), because function performs  |
//| initialization of some internal structures when called with these|
//| arguments.                                                       |
//+------------------------------------------------------------------+
void CDForest::EstimateVariableImportance(CDecisionForestBuilder &s,
                                          int sessionseed,
                                          CDecisionForest &df,
                                          int ntrees,
                                          CDFReport &rep)
  {
//--- create variables
   int npoints=0;
   int nvars=0;
   int nclasses=0;
   int nperm=0;
   int i=0;
   int j=0;
   int k=0;
   CRowDouble tmpr0;
   CRowDouble tmpr1;
   CRowInt tmpi0;
   vector<double> losses;
   CDFPermimpBuf permseed;
   double nopermloss=0;
   double totalpermloss=0;
   CHighQualityRandState varimprs;
   npoints=s.m_NPoints;
   nvars=s.m_NVars;
   nclasses=s.m_NClasses;
//--- No importance rating
   if(s.m_RDFImportance==0)
      return;
//--- Gini importance
   if(s.m_RDFImportance==m_NeedTrnGini || s.m_RDFImportance==m_NeedOOBGini)
     {
      //--- Merge OOB Gini importances computed during tree generation
      int total=ArraySize(s.m_VoteBuf);
      losses=s.m_VoteBuf[0].m_giniimportances.ToVector();
      for(int vote=1; vote<total; vote++)
         losses+=s.m_VoteBuf[vote].m_giniimportances.ToVector();
      losses.Resize(nvars);
      losses=losses/(double)ntrees;
      losses.Clip(0,1);
      rep.m_varimportances=losses;
      //--- Compute topvars[] array
      tmpr0=losses*(-1);
      for(i=0; i<nvars; i++)
         rep.m_topvars.Set(i,i);
      CTSort::TagSortFastI(tmpr0,rep.m_topvars,tmpr1,tmpi0,nvars);
      return;
     }
//--- Permutation importance
   if(s.m_RDFImportance==m_NeedPermutation)
     {
      if(!CAp::Assert(df.m_ForestFormat==m_DFUncompressedV0,__FUNCTION__": integrity check failed (ff)"))
         return;
      if(!CAp::Assert(s.m_IOBMatrix.Rows()>=ntrees && s.m_IOBMatrix.Cols()>=npoints,__FUNCTION__": integrity check failed (IOB)"))
         return;
      //--- Generate packed representation of the shuffle which is applied to all variables
      //--- Ideally we want to apply different permutations to different variables,
      //--- i.e. we have to generate and store NPoints*NVars random numbers.
      //--- However due to performance and memory restrictions we prefer to use compact
      //--- representation:
      //---*we store one "reference" permutation P_ref in VarImpShuffle2[0:NPoints-1]
      //--- * a permutation P_j applied to variable J is obtained by circularly shifting
      //---   elements in P_ref by VarImpShuffle2[NPoints+J]
      CHighQualityRand::HQRndSeed(sessionseed,1117,varimprs);
      s.m_VarImpShuffle2.Resize(npoints+nvars);
      for(i=0; i<npoints; i++)
         s.m_VarImpShuffle2.Set(i,i);
      for(i=0; i<npoints-1; i++)
        {
         j=i+CHighQualityRand::HQRndUniformI(varimprs,npoints-i);
         s.m_VarImpShuffle2.Swap(i,j);
        }
      for(i=0; i<nvars; i++)
         s.m_VarImpShuffle2.Set(npoints+i,CHighQualityRand::HQRndUniformI(varimprs,npoints));
      //--- Prepare buffer object, seed pool
      nperm=nvars+2;
      permseed.m_losses=vector<double>::Zeros(nperm);
      permseed.m_yv.Resize(nperm*nclasses);
      permseed.m_xraw.Resize(nvars);
      permseed.m_xdist.Resize(nvars);
      permseed.m_xcur.Resize(nvars);
      permseed.m_targety.Resize(nclasses);
      permseed.m_startnodes.Resize(nvars);
      permseed.m_y.Resize(nclasses);
      //--- Recursively split subset
      EstimatePermutationImportances(s,df,ntrees,permseed,0,npoints);
      //--- Merge results
      losses=permseed.m_losses+vector<double>::Full(nperm,1.0e-20);
      //--- Compute importances
      nopermloss=losses[nvars+1];
      totalpermloss=losses[nvars];
      losses=  losses/totalpermloss-nopermloss/totalpermloss;
      losses.Clip(0,1);
      losses.Resize(nvars);
      rep.m_varimportances=losses;
      //--- Compute topvars[] array
      tmpr0=losses*(-1);
      rep.m_topvars.Resize(nvars);
      for(j=0; j<nvars; j++)
         rep.m_topvars.Set(j,j);
      CTSort::TagSortFastI(tmpr0,rep.m_topvars,tmpr1,tmpi0,nvars);
      return;
     }
   CAp::Assert(false,__FUNCTION__": unexpected importance type");
  }
//+------------------------------------------------------------------+
//| Estimates permutation variable importance ratings for a range of |
//| dataset points.                                                  |
//| Initial call to this function should span entire range of the    |
//| dataset, [Idx0, Idx1) = [0, NPoints), because function performs  |
//| initialization of some internal structures when called with these|
//| arguments.                                                       |
//+------------------------------------------------------------------+
void CDForest::EstimatePermutationImportances(CDecisionForestBuilder &s,
                                              CDecisionForest &df,
                                              int ntrees,
                                              CDFPermimpBuf &permimpbuf,
                                              int idx0,
                                              int idx1)
  {
//--- create variables
   int    nperm=0;
   int    i=0;
   int    j=0;
   int    k=0;
   double v=0;
   int    treeroot=0;
   int    nodeoffs=0;
   double prediction=0;
   int    varidx=0;
   int    oobcounts=0;
   int    srcidx=0;
   int    npoints=s.m_NPoints;
   int    nvars=s.m_NVars;
   int    nclasses=s.m_NClasses;
//--- check
   if(!CAp::Assert(df.m_ForestFormat==m_DFUncompressedV0,__FUNCTION__": integrity check failed (ff)"))
      return;
   if(!CAp::Assert(idx0>=0 && idx0<=idx1 && idx1<=npoints,__FUNCTION__": integrity check failed (idx)"))
      return;
   if(!CAp::Assert(s.m_IOBMatrix.Rows()>=ntrees && s.m_IOBMatrix.Cols()>=npoints,__FUNCTION__": integrity check failed (IOB)"))
      return;
//--- main loop
   nperm=nvars+2;
//--- Process range of points [idx0,idx1)
   for(i=idx0; i<idx1-1; i++)
     {
      if(!CAp::Assert(s.m_DSType==0,__FUNCTION__": unexpected dataset type"))
         return;
      for(j=0; j<nvars; j++)
        {
         permimpbuf.m_xraw.Set(j,s.m_DSData[j*npoints+i]);
         srcidx=s.m_VarImpShuffle2[(i+s.m_VarImpShuffle2[npoints+j])%npoints];
         permimpbuf.m_xdist.Set(j,s.m_DSData[j*npoints+srcidx]);
        }
      if(nclasses>1)
        {
         permimpbuf.m_targety=vector<double>::Zeros(nclasses);
         permimpbuf.m_targety.Set(s.m_DSIVal[i],1);
        }
      else
         permimpbuf.m_targety.Set(0,s.m_DSRVal[i]);
      //--- Process all trees, for each tree compute NPerm losses corresponding
      //--- to various permutations of variable values
      permimpbuf.m_yv=vector<double>::Zeros(nperm*nclasses);
      oobcounts=0;
      treeroot=0;
      for(k=0; k<ntrees; k++)
        {
         if(!s.m_IOBMatrix.Get(k,i))
           {
            //--- Process original (unperturbed) point and analyze path from the
            //--- tree root to the final leaf. Output prediction to RawPrediction.
            //--- Additionally, for each variable in [0,NVars-1] save offset of
            //--- the first split on this variable. It allows us to quickly compute
            //--- tree decision when perturbation does not change decision path.
            if(!CAp::Assert(df.m_ForestFormat==m_DFUncompressedV0,__FUNCTION__": integrity check failed (ff)"))
               return;
            nodeoffs=treeroot+1;
            permimpbuf.m_startnodes.Fill(-1);
            prediction=0;
            while(true)
              {
               if(df.m_Trees[nodeoffs]==-1.0)
                 {
                  prediction=df.m_Trees[nodeoffs+1];
                  break;
                 }
               j=(int)MathRound(df.m_Trees[nodeoffs]);
               if(permimpbuf.m_startnodes[j]<0)
                  permimpbuf.m_startnodes.Set(j,nodeoffs);
               if(permimpbuf.m_xraw[j]<df.m_Trees[nodeoffs+1])
                  nodeoffs+=m_InnerNodeWidth;
               else
                  nodeoffs=treeroot+(int)MathRound(df.m_Trees[nodeoffs+2]);
              }
            //--- Save loss for unperturbed point
            varidx=nvars+1;
            if(nclasses>1)
              {
               j=(int)MathRound(prediction);
               permimpbuf.m_yv.Add(varidx*nclasses+j,1);
              }
            else
               permimpbuf.m_yv.Add(varidx,prediction);
            //--- Save loss for all variables being perturbed (XDist).
            //--- This loss is used as a reference loss when we compute R-squared.
            varidx=nvars;
            permimpbuf.m_y=vector<double>::Zeros(nclasses);
            DFProcessInternalUncompressed(df,treeroot,treeroot+1,permimpbuf.m_xdist,permimpbuf.m_y);
            permimpbuf.m_yv.Add(varidx*nclasses+j,permimpbuf.m_y[j]);
            //--- Compute losses for variable #VarIdx being perturbed. Quite an often decision
            //--- process does not actually depend on the variable #VarIdx (path from the tree
            //--- root does not include splits on this variable). In such cases we perform
            //--- quick exit from the loop with precomputed value.
            permimpbuf.m_xcur=permimpbuf.m_xraw;
            for(varidx=0; varidx<nvars; varidx++)
              {
               if(permimpbuf.m_startnodes[varidx]>=0)
                 {
                  //--- Path from tree root to the final leaf involves split on variable #VarIdx.
                  //--- Restart computation from the position first split on #VarIdx.
                  if(!CAp::Assert(df.m_ForestFormat==m_DFUncompressedV0,__FUNCTION__": integrity check failed (ff)"))
                     return;
                  permimpbuf.m_xcur.Set(varidx,permimpbuf.m_xdist[varidx]);
                  nodeoffs=permimpbuf.m_startnodes[varidx];
                  while(true)
                    {
                     if(df.m_Trees[nodeoffs]==-1.0)
                       {
                        if(nclasses>1)
                          {
                           j=(int)MathRound(df.m_Trees[nodeoffs+1]);
                           permimpbuf.m_yv.Add(varidx*nclasses+j,1.0);
                          }
                        else
                           permimpbuf.m_yv.Add(varidx,df.m_Trees[nodeoffs+1]);
                        break;
                       }
                     j=(int)MathRound(df.m_Trees[nodeoffs]);
                     if(permimpbuf.m_xcur[j]<df.m_Trees[nodeoffs+1])
                        nodeoffs+=m_InnerNodeWidth;
                     else
                        nodeoffs=treeroot+(int)MathRound(df.m_Trees[nodeoffs+2]);
                    }
                  permimpbuf.m_xcur.Set(varidx,permimpbuf.m_xraw[varidx]);
                 }
               else
                 {
                  //--- Path from tree root to the final leaf does NOT involve split on variable #VarIdx.
                  //--- Permutation does not change tree output, reuse already computed value.
                  if(nclasses>1)
                    {
                     j=(int)MathRound(prediction);
                     permimpbuf.m_yv.Add(varidx*nclasses+j,1.0);
                    }
                  else
                     permimpbuf.m_yv.Add(varidx,prediction);
                 }
              }
            //--- update OOB counter
            oobcounts++;
           }
         treeroot+=(int)MathRound(df.m_Trees[treeroot]);
        }
      //--- Now YV[] stores NPerm versions of the forest output for various permutations of variable values.
      //--- Update losses.
      for(j=0; j<nperm; j++)
        {
         if(oobcounts!=0)
            for(k=0; k<nclasses ; k++)
               permimpbuf.m_yv.Mul(j*nclasses+k,1.0/(double)oobcounts);
         v=0;
         for(k=0; k<=nclasses-1; k++)
            v=v+CMath::Sqr(permimpbuf.m_yv[j*nclasses+k]-permimpbuf.m_targety[k]);
         permimpbuf.m_losses.Add(j,v);
        }
     }
  }
//+------------------------------------------------------------------+
//| Sets report fields to their default values                       |
//+------------------------------------------------------------------+
void CDForest::CleanReport(CDecisionForestBuilder &s,CDFReport &rep)
  {
   rep.m_RelCLSError=0;
   rep.m_AvgCE=0;
   rep.m_RMSError=0;
   rep.m_AvgError=0;
   rep.m_AvgRelError=0;
   rep.m_oobrelclserror=0;
   rep.m_oobavgce=0;
   rep.m_oobrmserror=0;
   rep.m_oobavgerror=0;
   rep.m_oobavgrelerror=0;
   rep.m_topvars.Resize(s.m_NVars);
   rep.m_varimportances=vector<double>::Zeros(s.m_NVars);
   for(int i=0; i<=s.m_NVars-1; i++)
      rep.m_topvars.Set(i,i);
  }
//+------------------------------------------------------------------+
//| This function returns NRMS2 loss(sum of squared residuals) for a |
//| constant - output model:                                         |
//|   * model output is a mean over TRN set being passed (for        |
//|     classification problems - NClasses - dimensional vector      |
//|     of class probabilities)                                      |
//|   * model is evaluated over TST set being passed, with L2 loss   |
//|    being returned                                                |
//| Input parameters:                                                |
//|   NClasses   - ">1" for classification, "=1" for regression    |
//|   TrnLabelsI  -  training set labels, class indexes (for         |
//|                  NClasses > 1)                                   |
//|   TrnLabelsR  -  training set output values (for NClasses = 1)   |
//|   TrnIdx0,                                                       |
//|   TrnIdx1     -  a range [Idx0, Idx1) of elements in LabelsI/R   |
//|                  is considered                                   |
//|   TstLabelsI  -  training set labels, class indexes (for         |
//|                  NClasses > 1)                                   |
//|   TstLabelsR  -  training set output values (for NClasses = 1)   |
//|   TstIdx0,                                                       |
//|   TstIdx1     -  a range [Idx0, Idx1) of elements in LabelsI/R   |
//|                  is considered                                   |
//|   TmpI        -  temporary array, reallocated as needed          |
//| Result: sum of squared residuals; for NClasses >= 2 it coincides |
//|         with Gini impurity times(Idx1 - Idx0)                    |
//| Following fields of WorkBuf are used as temporaries:             |
//|      * TmpMeanNRMS2                                              |
//+------------------------------------------------------------------+
double CDForest::MeanNRMS2(int nclasses,CRowInt &trnlabelsi,
                           CRowDouble &trnlabelsr,int trnidx0,
                           int trnidx1,CRowInt &tstlabelsi,
                           CRowDouble &tstlabelsr,int tstidx0,
                           int tstidx1,CRowInt &tmpi)
  {
//--- create variables
   double result=0;
   int    i=0;
   int    k=0;
   int    ntrn=trnidx1-trnidx0;
   int    ntst=tstidx1-tstidx0;
   double v=0;
   double vv=0;
   double invntrn=0;
   double pitrn=0;
   double nitst=0;
//--- check
   if(!CAp::Assert(trnidx0<=trnidx1,__FUNCTION__": integrity check failed (8754)"))
      return(result);
   if(!CAp::Assert(tstidx0<=tstidx1,__FUNCTION__": integrity check failed (8754)"))
      return(result);
//--- Quick exit
   if(ntrn==0 || ntst==0)
      return(result);
   invntrn=1.0/(double)ntrn;
   if(nclasses>1)
     {
      //--- Classification problem
      tmpi.Resize(2*nclasses);
      tmpi.Fill(0);
      for(i=trnidx0; i<trnidx1; i++)
         tmpi.Add(trnlabelsi[i],1);
      for(i=tstidx0; i<tstidx1; i++)
         tmpi.Add(tstlabelsi[i]+nclasses,1);
      for(i=0; i<nclasses; i++)
        {
         pitrn=tmpi[i]*invntrn;
         nitst=tmpi[i+nclasses];
         result+=nitst+pitrn*(ntst*pitrn-2*nitst);
        }
     }
   else
     {
      //--- regression-specific code
      v=0;
      for(i=trnidx0; i<trnidx1; i++)
         v+=trnlabelsr[i];
      v=v*invntrn;
      for(i=tstidx0; i<tstidx1 ; i++)
        {
         vv=tstlabelsr[i]-v;
         result+=vv*vv;
        }
     }
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| This function is a part of the recurrent tree construction       |
//| function; it selects variable for splitting according to current |
//| tree construction algorithm.                                     |
//| Note: modifies VarsInPool, may decrease it if some variables     |
//|       become non-informative and leave the pool.                 |
//+------------------------------------------------------------------+
void CDForest::ChooseCurrentSplitDense(CDecisionForestBuilder &s,
                                       CDFWorkBuf &workbuf,
                                       int &varsinpool,
                                       int varstoselect,
                                       CHighQualityRandState &rs,
                                       int idx0,
                                       int idx1,
                                       int &varbest,
                                       double &splitbest)
  {
//--- create variables
   int    npoints=0;
   double errbest=0;
   int    varstried=0;
   int    varcur=0;
   bool   valuesaresame;
   int    offs=0;
   double split=0;
   int    i=0;
   double v=0;
   double v0=0;
   double currms=0;
   int    info=0;

   varbest=0;
   splitbest=0;
//---check
   if(!CAp::Assert(s.m_DSType==0,__FUNCTION__": sparsity is not supported 4terg!"))
      return;
   if(!CAp::Assert(s.m_RDFAlgo==0,__FUNCTION__": integrity check failed (1657)"))
      return;
   if(!CAp::Assert(idx0<idx1,__FUNCTION__": integrity check failed (3445)"))
      return;
   npoints=s.m_NPoints;
//--- Select split according to dense direct RDF algorithm
   varbest=-1;
   errbest=CMath::m_maxrealnumber;
   splitbest=0;
   varstried=0;
   while(varstried<MathMin(varstoselect,varsinpool))
     {
      //--- select variables from pool
      workbuf.m_varpool.Swap(varstried,varstried+CHighQualityRand::HQRndUniformI(rs,varsinpool-varstried));
      varcur=workbuf.m_varpool[varstried];
      //--- Load variable values to working array.
      //--- If all variable values are same, variable is excluded from pool and we re-run variable selection.
      valuesaresame=true;
      if(!CAp::Assert(s.m_DSType==0,"not supported segsv34fs"))
         return;
      offs=npoints*varcur;
      v0=s.m_DSData[offs+workbuf.m_trnset[idx0]];
      for(i=idx0; i<idx1; i++)
        {
         v=s.m_DSData[offs+workbuf.m_trnset[i]];
         workbuf.m_curvals.Set(i,v);
         valuesaresame=valuesaresame && v==v0;
        }
      if(valuesaresame)
        {
         //--- Variable does not change across current subset.
         //--- Exclude variable from pool, go to the next iteration.
         //--- VarsTried is not increased.
         //--- NOTE: it is essential that updated VarsInPool is passed
         //---       down to children but not up to caller - it is
         //---       possible that one level higher this variable is
         //---       not-fixed.
         workbuf.m_varpool.Swap(varstried,varsinpool-1);
         varsinpool=varsinpool-1;
         continue;
        }
      //--- Now we are ready to infer the split
      EvaluateDenseSplit(s,workbuf,rs,varcur,idx0,idx1,info,split,currms);
      if(info>0 && (varbest<0 || currms<=errbest))
        {
         errbest=currms;
         varbest=varcur;
         splitbest=split;
         for(i=idx0; i<idx1; i++)
            workbuf.m_bestvals.Set(i,workbuf.m_curvals[i]);
        }
      //--- Next iteration
      varstried=varstried+1;
     }
  }
//+------------------------------------------------------------------+
//| This function performs split on some specific dense variable     |
//| whose values are stored in WorkBuf.CurVals[Idx0, Idx1) and labels|
//| are stored in WorkBuf.TrnLabelsR / I[Idx0, Idx1).                |
//| It returns split value and associated RMS error. It is           |
//| responsibility of the caller to make sure that variable has at   |
//| least two distinct values, i.e. it is possible to make a split.  |
//| Precomputed values of following fields of WorkBuf are used:      |
//|      * ClassPriors                                               |
//| Following fields of WorkBuf are used as temporaries:             |
//|      * ClassTotals0, 1, 01                                       |
//|      * Tmp0I, Tmp1I, Tmp0R, Tmp1R, Tmp2R, Tmp3R                  |
//+------------------------------------------------------------------+
void CDForest::EvaluateDenseSplit(CDecisionForestBuilder &s,
                                  CDFWorkBuf &workbuf,
                                  CHighQualityRandState &rs,
                                  int splitvar,
                                  int idx0,
                                  int idx1,
                                  int &info,
                                  double &split,
                                  double &rms)
  {
//--- create variables
   int    nclasses=0;
   int    i=0;
   int    j=0;
   int    k0=0;
   int    k1=0;
   double v=0;
   double v0=0;
   double v1=0;
   double v2=0;
   int    sl=0;
   int    sr=0;

   info=0;
   split=0;
   rms=0;
   if(!CAp::Assert(idx0<idx1,__FUNCTION__": integrity check failed (8754)"))
      return;

   nclasses=s.m_NClasses;
   if(s.m_DSBinary[splitvar])
     {
      //--- Try simple binary split, if possible
      //--- Split can be inferred from minimum/maximum values, just calculate RMS error
      info=1;
      split=GetSplit(s,s.m_DSMin[splitvar],s.m_DSMax[splitvar],rs);
      if(nclasses>1)
        {
         //--- Classification problem
         workbuf.m_classtotals0.Fill(0);
         sl=0;
         for(i=idx0; i<idx1; i++)
           {
            if(workbuf.m_curvals[i]<split)
              {
               j=workbuf.m_trnlabelsi[i];
               workbuf.m_classtotals0.Add(j,1);
               sl++;
              }
           }
         sr=idx1-idx0-sl;
         if(!CAp::Assert(sl!=0 && sr!=0,__FUNCTION__": something strange,impossible failure!"))
            return;
         v0=1.0/(double)sl;
         v1=1.0/(double)sr;
         rms=0;
         for(j=0; j<nclasses; j++)
           {
            k0=workbuf.m_classtotals0[j];
            k1=workbuf.m_classpriors[j]-k0;
            rms+=k0*(1-v0*k0)+k1*(1-v1*k1);
           }
         rms=MathSqrt(rms/(nclasses*(idx1-idx0+1)));
        }
      else
        {
         //--- regression-specific code
         sl=0;
         sr=0;
         v1=0;
         v2=0;
         for(j=idx0; j<idx1; j++)
           {
            if(workbuf.m_curvals[j]<split)
              {
               v1+=workbuf.m_trnlabelsr[j];
               sl++;
              }
            else
              {
               v2+=workbuf.m_trnlabelsr[j];
               sr++;
              }
           }
         if(!CAp::Assert(sl!=0 && sr!=0,__FUNCTION__": something strange,impossible failure!"))
            return;
         v1=v1/sl;
         v2=v2/sr;
         rms=0;
         for(j=0; j<idx1-idx0; j++)
           {
            v=workbuf.m_trnlabelsr[idx0+j];
            if(workbuf.m_curvals[j]<split)
               v-=v1;
            else
               v-=v2;
            rms+=v*v;
           }
         rms=MathSqrt(rms/(idx1-idx0+1));
        }
     }
   else
     {
      //--- General split
      info=0;
      if(nclasses>1)
        {
         for(i=0; i<idx1-idx0; i++)
           {
            workbuf.m_tmp0r.Set(i,workbuf.m_curvals[idx0+i]);
            workbuf.m_tmp0i.Set(i,workbuf.m_trnlabelsi[idx0+i]);
           }
         ClassifierSplit(s,workbuf,workbuf.m_tmp0r,workbuf.m_tmp0i,idx1-idx0,rs,info,split,rms,workbuf.m_tmp1r,workbuf.m_tmp1i);
        }
      else
        {
         for(i=0; i<idx1-idx0; i++)
           {
            workbuf.m_tmp0r.Set(i,workbuf.m_curvals[idx0+i]);
            workbuf.m_tmp1r.Set(i,workbuf.m_trnlabelsr[idx0+i]);
           }
         RegressionSplit(s,workbuf,workbuf.m_tmp0r,workbuf.m_tmp1r,idx1-idx0,info,split,rms,workbuf.m_tmp2r,workbuf.m_tmp3r);
        }
     }
  }
//+------------------------------------------------------------------+
//| Classifier split                                                 |
//+------------------------------------------------------------------+
void CDForest::ClassifierSplit(CDecisionForestBuilder &s,
                               CDFWorkBuf &workbuf,
                               CRowDouble &x,
                               CRowInt &c,
                               int n,
                               CHighQualityRandState &rs,
                               int &info,
                               double &threshold,
                               double &e,
                               CRowDouble &sortrbuf,
                               CRowInt &sortibuf)
  {
//--- create variables
   int    i=0;
   int    j=0;
   int    k=0;
   int    n0=0;
   int    n0prev=0;
   double v=0;
   int    advanceby=0;
   double rms=0;
   int    k0=0;
   int    k1=0;
   double v0=0;
   double v1=0;
   int    nclasses=0;
   double vmin=0;
   double vmax=0;

   info=0;
   threshold=0;
   e=0;
//--- check
   if(!CAp::Assert(s.m_RDFSplitStrength==0 || s.m_RDFSplitStrength==1 || s.m_RDFSplitStrength==2,__FUNCTION__": unexpected split type at ClassifierSplit()"))
      return;

   nclasses=s.m_NClasses;
   advanceby=1;
   if(n>=20)
      advanceby=MathMax(2,(int)MathRound(n*0.05));
   info=-1;
   threshold=0;
   e=CMath::m_maxrealnumber;
//--- Random split
   if(s.m_RDFSplitStrength==0)
     {
      //--- Evaluate minimum, maximum and randomly selected values
      vmin=x[0];
      vmax=x[0];
      for(i=1; i<n; i++)
        {
         v=x[i];
         if(v<vmin)
            vmin=v;
         if(v>vmax)
            vmax=v;
        }
      if(vmin==vmax)
         return;
      v=x[CHighQualityRand::HQRndUniformI(rs,n)];
      if(v==vmin)
         v=vmax;
      //--- Calculate RMS error associated with the split
      workbuf.m_classtotals0.Fill(0,0,nclasses);
      n0=0;
      for(i=0; i<n; i++)
         if(x[i]<v)
           {
            k=c[i];
            workbuf.m_classtotals0.Add(k,1);
            n0++;
           }
      if(!CAp::Assert(n0>0 && n0<n,__FUNCTION__": critical integrity check failed at ClassifierSplit()"))
         return;
      v0=1.0/(double)n0;
      v1=1.0/(double)(n-n0);
      rms=0;
      for(j=0; j<nclasses; j++)
        {
         k0=workbuf.m_classtotals0[j];
         k1=workbuf.m_classpriors[j]-k0;
         rms+=k0*(1-v0*k0)+k1*(1-v1*k1);
        }
      threshold=v;
      info=1;
      e=rms;
      return;
     }
//--- Stronger splits which require us to sort the data
//--- Quick check for degeneracy
   CTSort::TagSortFastI(x,c,sortrbuf,sortibuf,n);
   v=0.5*(x[0]+x[n-1]);
   if(!(x[0]<v && v<x[n-1]))
      return;
   switch(s.m_RDFSplitStrength)
     {
      //--- Split at the middle
      case 1:
         //--- Select split position
         vmin=x[0];
         vmax=x[n-1];
         v=x[n/2];
         if(v==vmin)
            v=vmin+0.001*(vmax-vmin);
         if(v==vmin)
            v=vmax;
         //--- Calculate RMS error associated with the split
         workbuf.m_classtotals0.Fill(0,0,nclasses);
         n0=0;
         for(i=0; i<n; i++)
            if(x[i]<v)
              {
               k=c[i];
               workbuf.m_classtotals0.Add(k,1);
               n0++;
              }
         if(!CAp::Assert(n0>0 && n0<n,__FUNCTION__": critical integrity check failed at ClassifierSplit()"))
            return;
         v0=1.0/(double)n0;
         v1=1.0/(double)(n-n0);
         rms=0;
         for(j=0; j<nclasses; j++)
           {
            k0=workbuf.m_classtotals0[j];
            k1=workbuf.m_classpriors[j]-k0;
            rms+=k0*(1-v0*k0)+k1*(1-v1*k1);
           }
         threshold=v;
         info=1;
         e=rms;
         break;
      //--- Strong split
      case 2:
         //--- Prepare initial split.
         //--- Evaluate current split, prepare next one, repeat.
         workbuf.m_classtotals0.Fill(0,0,nclasses);
         n0=1;
         while(n0<n && x[n0]==x[n0-1])
            n0++;
         if(!CAp::Assert(n0<n,__FUNCTION__": critical integrity check failed in ClassifierSplit()"))
            return;
         for(i=0; i<n0; i++)
           {
            k=c[i];
            workbuf.m_classtotals0.Add(k,1);
           }
         info=-1;
         threshold=x[n-1];
         e=CMath::m_maxrealnumber;
         while(n0<n)
           {
            //--- RMS error associated with current split
            v0=1.0/(double)n0;
            v1=1.0/(double)(n-n0);
            rms=0;
            for(j=0; j<nclasses; j++)
              {
               k0=workbuf.m_classtotals0[j];
               k1=workbuf.m_classpriors[j]-k0;
               rms+=k0*(1-v0*k0)+k1*(1-v1*k1);
              }
            if(info<0 || rms<e)
              {
               info=1;
               e=rms;
               threshold=0.5*(x[n0-1]+x[n0]);
               if(threshold<=x[n0-1])
                  threshold=x[n0];
              }
            //--- Advance
            n0prev=n0;
            while(n0<n && (n0-n0prev)<advanceby)
              {
               v=x[n0];
               while(n0<n && x[n0]==v)
                 {
                  k=c[n0];
                  workbuf.m_classtotals0.Add(k,1);
                  n0++;
                 }
              }
           }
         if(info>0)
            e=MathSqrt(e/(nclasses*n));
         break;
      default:
         CAp::Assert(false,__FUNCTION__": ClassifierSplit(),critical error");
         break;
     }
  }
//+------------------------------------------------------------------+
//| Regression model split                                           |
//+------------------------------------------------------------------+
void CDForest::RegressionSplit(CDecisionForestBuilder &s,
                               CDFWorkBuf &workbuf,
                               CRowDouble &x,
                               CRowDouble &y,
                               int n,
                               int &info,
                               double &threshold,
                               double &e,
                               CRowDouble &sortrbuf,
                               CRowDouble &sortrbuf2)
  {
//--- create variables
   int    i=0;
   double vmin=0;
   double vmax=0;
   double bnd01=0;
   double bnd12=0;
   double bnd23=0;
   int    total0=0;
   int    total1=0;
   int    total2=0;
   int    total3=0;
   int    cnt0=0;
   int    cnt1=0;
   int    cnt2=0;
   int    cnt3=0;
   int    n0=0;
   int    advanceby=0;
   double v=0;
   double v0=0;
   double v1=0;
   double rms=0;
   int    n0prev=0;
   int    k0=0;
   int    k1=0;

   info=0;
   threshold=0;
   e=0;
   advanceby=1;
   if(n>=20)
      advanceby=MathMax(2,(int)MathRound(n*0.05));
//--- Sort data
//--- Quick check for degeneracy
   CTSort::TagSortFastR(x,y,sortrbuf,sortrbuf2,n);
   v=0.5*(x[0]+x[n-1]);
   if(!(x[0]<v && v<x[n-1]))
     {
      info=-1;
      threshold=x[n-1];
      e=CMath::m_maxrealnumber;
      return;
     }
//--- Prepare initial split.
//--- Evaluate current split, prepare next one, repeat.
   vmin=y[0];
   vmax=y[0];
   for(i=1; i<n; i++)
     {
      v=y[i];
      if(v<vmin)
         vmin=v;
      if(v>vmax)
         vmax=v;
     }
   bnd12=0.5*(vmin+vmax);
   bnd01=0.5*(vmin+bnd12);
   bnd23=0.5*(vmax+bnd12);
   total0=0;
   total1=0;
   total2=0;
   total3=0;
   for(i=0; i<n; i++)
     {
      v=y[i];
      if(v<bnd12)
        {
         if(v<bnd01)
            total0++;
         else
            total1++;
        }
      else
        {
         if(v<bnd23)
            total2++;
         else
            total3++;
        }
     }
   n0=1;
   while(n0<n && x[n0]==x[n0-1])
      n0++;
   if(!CAp::Assert(n0<n,__FUNCTION__": critical integrity check failed in ClassifierSplit()"))
      return;

   cnt0=0;
   cnt1=0;
   cnt2=0;
   cnt3=0;
   for(i=0; i<n0; i++)
     {
      v=y[i];
      if(v<bnd12)
        {
         if(v<bnd01)
            cnt0++;
         else
            cnt1++;
        }
      else
        {
         if(v<bnd23)
            cnt2++;
         else
            cnt3++;
        }
     }

   info=-1;
   threshold=x[n-1];
   e=CMath::m_maxrealnumber;
   while(n0<n)
     {
      //--- RMS error associated with current split
      v0=1.0/(double)n0;
      v1=1.0/(double)(n-n0);
      rms=0;
      k0=cnt0;
      k1=total0-cnt0;
      rms+=k0*(1-v0*k0)+k1*(1-v1*k1);
      k0=cnt1;
      k1=total1-cnt1;
      rms+=k0*(1-v0*k0)+k1*(1-v1*k1);
      k0=cnt2;
      k1=total2-cnt2;
      rms+=k0*(1-v0*k0)+k1*(1-v1*k1);
      k0=cnt3;
      k1=total3-cnt3;
      rms=rms+k0*(1-v0*k0)+k1*(1-v1*k1);
      if(info<0 || rms<e)
        {
         info=1;
         e=rms;
         threshold=0.5*(x[n0-1]+x[n0]);
         if(threshold<=x[n0-1])
            threshold=x[n0];
        }
      //--- Advance
      n0prev=n0;
      while(n0<n && (n0-n0prev)<advanceby)
        {
         v0=x[n0];
         while(n0<n && x[n0]==v0)
           {
            v=y[n0];
            if(v<bnd12)
              {
               if(v<bnd01)
                  cnt0++;
               else
                  cnt1++;
              }
            else
              {
               if(v<bnd23)
                  cnt2++;
               else
                  cnt3++;
              }
            n0++;
           }
        }
     }

   if(info>0)
      e=MathSqrt(e/(4*n));
  }
//+------------------------------------------------------------------+
//| Returns split: either deterministic split at the middle          |
//| of [A, B], or randomly chosen split.                             |
//| It is guaranteed that A < Split <= B.                            |
//+------------------------------------------------------------------+
double CDForest::GetSplit(CDecisionForestBuilder &s,double a,
                          double b,CHighQualityRandState &rs)
  {
   double result=0.5*(a+b);
//--- check
   if(result<=a)
      result=b;
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| Outputs leaf to the tree                                         |
//| Following items of TRN and OOB sets are updated in the voting    |
//| buffer:                                                          |
//|   * items [Idx0, Idx1) of WorkBuf.TrnSet                         |
//|   * items [OOBIdx0, OOBIdx1) of WorkBuf.OOBSet                   |
//+------------------------------------------------------------------+
void CDForest::OutputLeaf(CDecisionForestBuilder &s,
                          CDFWorkBuf &workbuf,
                          CRowDouble &treebuf,
                          CDFVoteBuf &votebuf,
                          int idx0,
                          int idx1,
                          int oobidx0,
                          int oobidx1,
                          int &treesize,
                          double leafval)
  {
//--- create variables
   int nclasses=s.m_NClasses;
   int leafvali=0;
   int i=0;
   int j=0;

   if(nclasses==1)
     {
      //--- Store split to the tree
      treebuf.Set(treesize,-1);
      treebuf.Set(treesize+1,leafval);
      //--- Update training and OOB voting stats
      for(i=idx0; i<idx1; i++)
        {
         j=workbuf.m_trnset[i];
         votebuf.m_trntotals.Add(j,leafval);
         votebuf.m_trncounts.Add(j,1);
        }
      for(i=oobidx0; i<oobidx1; i++)
        {
         j=workbuf.m_oobset[i];
         votebuf.m_oobtotals.Add(j,leafval);
         votebuf.m_oobcounts.Add(j,1);
        }
     }
   else
     {
      //--- Store split to the tree
      treebuf.Set(treesize,-1);
      treebuf.Set(treesize+1,leafval);
      //--- Update training and OOB voting stats
      leafvali=(int)MathRound(leafval);
      for(i=idx0; i<idx1; i++)
        {
         j=workbuf.m_trnset[i];
         votebuf.m_trntotals.Add(j*nclasses+leafvali,1);
         votebuf.m_trncounts.Add(j,1);
        }
      for(i=oobidx0; i<=oobidx1-1; i++)
        {
         j=workbuf.m_oobset[i];
         votebuf.m_oobtotals.Add(j*nclasses+leafvali,1);
         votebuf.m_oobcounts.Add(j,1);
        }
     }
   treesize+=m_LeafNodeWdth;
  }
//+------------------------------------------------------------------+
//| This function performs generic and algorithm - specific          |
//| preprocessing of the dataset                                     |
//+------------------------------------------------------------------+
void CDForest::AnalyzeAndPreprocessDataset(CDecisionForestBuilder &s)
  {
//--- check
   if(!CAp::Assert(s.m_DSType==0,__FUNCTION__": no sparsity"))
      return;
//--- create variables
   int i=0;
   int j=0;
   bool isbinary;
   double v=0;
   double v0=0;
   double v1=0;
   CHighQualityRandState rs;
   int npoints=s.m_NPoints;
   int nvars=s.m_NVars;
   int nclasses=s.m_NClasses;
//--- seed local RNG
   if(s.m_RDFGlobalSeed>0)
      CHighQualityRand::HQRndSeed(s.m_RDFGlobalSeed,3532,rs);
   else
      CHighQualityRand::HQRndSeed(CMath::RandomInteger(30000),3532,rs);
//--- Generic processing
   if(!CAp::Assert(npoints>=1,__FUNCTION__": integrity check failed"))
      return;
   s.m_DSMin.Resize(nvars);
   s.m_DSMax.Resize(nvars);
   CApServ::BVectorSetLengthAtLeast(s.m_DSBinary,nvars);
   for(i=0; i<nvars; i++)
     {
      v0=s.m_DSData[i*npoints];
      v1=s.m_DSData[i*npoints];
      for(j=1; j<npoints; j++)
        {
         v=s.m_DSData[i*npoints+j];
         if(v<v0)
            v0=v;
         if(v>v1)
            v1=v;
        }
      s.m_DSMin.Set(i,v0);
      s.m_DSMax.Set(i,v1);
      if(!CAp::Assert(v0<=v1,__FUNCTION__": strange integrity check failure"))
         return;
      isbinary=true;
      for(j=0; j<npoints; j++)
        {
         v=s.m_DSData[i*npoints+j];
         isbinary=isbinary && (v==v0 || v==v1);
        }
      s.m_DSBinary[i]=isbinary;
     }
   if(nclasses==1)
     {
      s.m_DSRAvg=0;
      for(i=0; i<npoints; i++)
         s.m_DSRAvg+=s.m_DSRVal[i];
      s.m_DSRAvg=s.m_DSRAvg/npoints;
     }
   else
     {
      s.m_DSCTotals.Resize(nclasses);
      s.m_DSCTotals.Fill(0);
      for(i=0; i<npoints; i++)
         s.m_DSCTotals.Add(s.m_DSIVal[i],1);
     }
  }
//+------------------------------------------------------------------+
//| This function merges together trees generated during training and|
//| outputs it to the decision forest.                               |
//| INPUT PARAMETERS:                                                |
//|   S        -  decision forest builder object                     |
//|   NTrees   -  NTrees >= 1, number of trees to train              |
//| OUTPUT PARAMETERS:                                               |
//|   DF       -  decision forest                                    |
//|   Rep      -  report                                             |
//+------------------------------------------------------------------+
void CDForest::MergeTrees(CDecisionForestBuilder &s,
                          CDecisionForest &df)
  {
//--- create variables
   int i=0;
   int cursize=0;
   int offs=0;
   CRowInt treesizes;
   CRowInt treeoffsets;
//--- initialize
   df.m_ForestFormat=m_DFUncompressedV0;
   df.m_NVars=s.m_NVars;
   df.m_NClasses=s.m_NClasses;
   df.m_BufSize=0;
//--- Determine trees count
   df.m_NTrees=ArraySize(s.m_TreeBuf);
   if(!CAp::Assert(df.m_NTrees>0,__FUNCTION__": integrity check failed,zero trees count"))
      return;
//--- Determine individual tree sizes and total buffer size
   treesizes.Resize(df.m_NTrees);
   treesizes.Fill(-1);
   for(int idx=0; idx<df.m_NTrees; idx++)
     {
      if(!CAp::Assert(s.m_TreeBuf[idx].m_treeidx>=0 && s.m_TreeBuf[idx].m_treeidx<df.m_NTrees,__FUNCTION__": integrity check failed (wrong TreeIdx)"))
         return;
      if(!CAp::Assert(treesizes[s.m_TreeBuf[idx].m_treeidx]<0,__FUNCTION__": integrity check failed (duplicate TreeIdx)"))
         return;
      int size=(int)MathRound(s.m_TreeBuf[idx].m_treebuf[0]);
      if(!CAp::Assert(size>0,__FUNCTION__": integrity check failed (wrong TreeSize)"))
         return;
      df.m_BufSize+=size;
      treesizes.Set(s.m_TreeBuf[idx].m_treeidx,size);
     }
//--- Determine offsets for individual trees in output buffer
   treeoffsets.Resize(df.m_NTrees);
   treeoffsets.Set(0,0);
   for(i=1; i<df.m_NTrees; i++)
      treeoffsets.Set(i,treeoffsets[i-1]+treesizes[i-1]);
//--- Output trees
//--- NOTE: since ALGLIB 3.16.0 trees are sorted by tree index prior to
//---       output (necessary for variable importance estimation), that's
//---       why we need array of tree offsets
   df.m_Trees.Resize(df.m_BufSize);
   for(int idx=0; idx<df.m_NTrees; idx++)
     {
      cursize=(int)MathRound(s.m_TreeBuf[idx].m_treebuf[0]);
      offs=treeoffsets[s.m_TreeBuf[idx].m_treeidx];
      for(i=0; i<cursize; i++)
         df.m_Trees.Set(offs+i,s.m_TreeBuf[idx].m_treebuf[i]);
     }
  }
//+------------------------------------------------------------------+
//| This function post - processes voting array and calculates TRN   |
//| and OOB errors.                                                  |
//| INPUT PARAMETERS:                                                |
//|   S        -  decision forest builder object                     |
//|   NTrees   -  number of trees in the forest                      |
//|   Buf      -  possibly preallocated vote buffer, its contents is |
//|               overwritten by this function                       |
//| OUTPUT PARAMETERS:                                               |
//|   Rep      -  report fields corresponding to errors are updated  |
//+------------------------------------------------------------------+
void CDForest::ProcessVotingResults(CDecisionForestBuilder &s,
                                    int ntrees,
                                    CDFVoteBuf &buf,
                                    CDFReport &rep)
  {
//--- create variables
   int    i=0;
   int    j=0;
   int    k=0;
   int    k1=0;
   double v=0;
   int    avgrelcnt=0;
   int    oobavgrelcnt=0;
   int    npoints=s.m_NPoints;
   int    nvars=s.m_NVars;
   int    nclasses=s.m_NClasses;
//--- check
   if(!CAp::Assert(npoints>0,__FUNCTION__": integrity check failed"))
      return;
   if(!CAp::Assert(nvars>0,__FUNCTION__": integrity check failed"))
      return;
   if(!CAp::Assert(nclasses>0,__FUNCTION__": integrity check failed"))
      return;
//--- Prepare vote buffer
   buf.m_trntotals=vector<double>::Zeros(npoints*nclasses);
   buf.m_oobtotals=vector<double>::Zeros(npoints*nclasses);
   buf.m_trncounts.Resize(npoints);
   buf.m_oobcounts.Resize(npoints);
   buf.m_trncounts.Fill(0);
   buf.m_oobcounts.Fill(0);
//--- Merge voting arrays
   for(int idx=0; idx<ntrees; idx++)
     {
      buf.m_trntotals+=s.m_VoteBuf[idx].m_trntotals+s.m_VoteBuf[idx].m_oobtotals+0;
      buf.m_oobtotals+=s.m_VoteBuf[idx].m_oobtotals;
      for(i=0; i<=npoints-1; i++)
        {
         buf.m_trncounts.Add(i,s.m_VoteBuf[idx].m_trncounts[i]+s.m_VoteBuf[idx].m_oobcounts[i]);
         buf.m_oobcounts.Add(i,s.m_VoteBuf[idx].m_oobcounts[i]);
        }
     }
   for(i=0; i<npoints; i++)
     {
      v=1.0/CApServ::Coalesce(buf.m_trncounts[i],1);
      for(j=0; j<nclasses; j++)
         buf.m_trntotals.Mul(i*nclasses+j,v);
      v=1.0/CApServ::Coalesce(buf.m_oobcounts[i],1);
      for(j=0; j<=nclasses-1; j++)
         buf.m_oobtotals.Mul(i*nclasses+j,v);
     }
//--- Use aggregated voting data to output error metrics
   avgrelcnt=0;
   oobavgrelcnt=0;
   rep.m_RMSError=0;
   rep.m_AvgError=0;
   rep.m_AvgRelError=0;
   rep.m_RelCLSError=0;
   rep.m_AvgCE=0;
   rep.m_oobrmserror=0;
   rep.m_oobavgerror=0;
   rep.m_oobavgrelerror=0;
   rep.m_oobrelclserror=0;
   rep.m_oobavgce=0;
   for(i=0; i<npoints; i++)
     {
      if(nclasses>1)
        {
         //--- classification-specific code
         k=s.m_DSIVal[i];
         for(j=0; j<nclasses; j++)
           {
            v=buf.m_trntotals[i*nclasses+j];
            if(j==k)
              {
               rep.m_AvgCE-=MathLog(CApServ::Coalesce(v,CMath::m_minrealnumber));
               rep.m_RMSError+=CMath::Sqr(v-1);
               rep.m_AvgError+=MathAbs(v-1);
               rep.m_AvgRelError+=MathAbs(v-1);
               avgrelcnt++;
              }
            else
              {
               rep.m_RMSError+=CMath::Sqr(v);
               rep.m_AvgError+=MathAbs(v);
              }
            v=buf.m_oobtotals[i*nclasses+j];
            if(j==k)
              {
               rep.m_oobavgce-=MathLog(CApServ::Coalesce(v,CMath::m_minrealnumber));
               rep.m_oobrmserror+=CMath::Sqr(v-1);
               rep.m_oobavgerror+=MathAbs(v-1);
               rep.m_oobavgrelerror+=MathAbs(v-1);
               oobavgrelcnt++;
              }
            else
              {
               rep.m_oobrmserror+=CMath::Sqr(v);
               rep.m_oobavgerror+=MathAbs(v);
              }
           }
         //--- Classification errors are handled separately
         k1=0;
         for(j=1; j<nclasses; j++)
           {
            if(buf.m_trntotals[i*nclasses+j]>buf.m_trntotals[i*nclasses+k1])
               k1=j;
           }
         if(k1!=k)
            rep.m_RelCLSError++;
         k1=0;
         for(j=1; j<nclasses; j++)
           {
            if(buf.m_oobtotals[i*nclasses+j]>buf.m_oobtotals[i*nclasses+k1])
               k1=j;
           }
         if(k1!=k)
            rep.m_oobrelclserror++;
        }
      else
        {
         //--- regression-specific code
         v=buf.m_trntotals[i]-s.m_DSRVal[i];
         rep.m_RMSError+=CMath::Sqr(v);
         rep.m_AvgError+=MathAbs(v);
         if(s.m_DSRVal[i]!=0.0)
           {
            rep.m_AvgRelError+=MathAbs(v/s.m_DSRVal[i]);
            avgrelcnt++;
           }
         v=buf.m_oobtotals[i]-s.m_DSRVal[i];
         rep.m_oobrmserror+=CMath::Sqr(v);
         rep.m_oobavgerror+=MathAbs(v);
         if(s.m_DSRVal[i]!=0.0)
           {
            rep.m_oobavgrelerror+=MathAbs(v/s.m_DSRVal[i]);
            oobavgrelcnt++;
           }
        }
     }
   rep.m_RelCLSError/=npoints;
   rep.m_RMSError=MathSqrt(rep.m_RMSError/(npoints*nclasses));
   rep.m_AvgError/=(npoints*nclasses);
   rep.m_AvgRelError/=CApServ::Coalesce(avgrelcnt,1);
   rep.m_oobrelclserror/=npoints;
   rep.m_oobrmserror=MathSqrt(rep.m_oobrmserror/(npoints*nclasses));
   rep.m_oobavgerror/=(npoints*nclasses);
   rep.m_oobavgrelerror/=CApServ::Coalesce(oobavgrelcnt,1);
  }
//+------------------------------------------------------------------+
//| This function performs binary compression of decision forest,    |
//| using either 8-bit mantissa (a bit more compact representation)  |
//| or 16-bit mantissa for splits and regression outputs.            |
//| Forest is compressed in - place.                                 |
//| Return value is a compression factor.                            |
//+------------------------------------------------------------------+
double CDForest::BinaryCompression(CDecisionForest &df,
                                   bool usemantissa8)
  {
//--- create variables
   double result=0;
   int    size8=0;
   int    size8i=0;
   int    offssrc=0;
   int    offsdst=0;
   int    maxrawtreesize=0;
   CRowInt dummyi;
   CRowInt compressedsizes;
//--- Quick exit if already compressed
   if(df.m_ForestFormat==m_DFCompressedV0)
      return(1.0);
//--- Check that source format is supported
   if(!CAp::Assert(df.m_ForestFormat==m_DFUncompressedV0,__FUNCTION__": unexpected forest format"))
      return(result);
//--- Compute sizes of uncompressed and compressed trees.
   for(int i=0; i<df.m_NTrees; i++)
     {
      size8i=ComputeCompressedSizeRec(df,usemantissa8,offssrc,offssrc+1,dummyi,false);
      size8+=ComputeCompressedUintSize(size8i)+size8i;
      maxrawtreesize=MathMax(maxrawtreesize,(int)MathRound(df.m_Trees[offssrc]));
      offssrc+=(int)MathRound(df.m_Trees[offssrc]);
     }
   result=(double)(8.0*df.m_Trees.Size())/(double)(size8+1.0);
//--- Allocate memory and perform compression
   df.m_Trees8.Resize(size8);
   compressedsizes.Resize(maxrawtreesize);
   offssrc=0;
   offsdst=0;
   for(int i=0; i<df.m_NTrees; i++)
     {
      //--- Call compressed size evaluator one more time, now saving subtree sizes into temporary array
      size8i=ComputeCompressedSizeRec(df,usemantissa8,offssrc,offssrc+1,compressedsizes,true);
      //--- Output tree header (length in bytes)
      StreamUint(df.m_Trees8,offsdst,size8i);
      //--- Compress recursively
      CompressRec(df,usemantissa8,offssrc,offssrc+1,compressedsizes,df.m_Trees8,offsdst);
      //--- Next tree
      offssrc+=(int)MathRound(df.m_Trees[offssrc]);
     }
   if(!CAp::Assert(offsdst==size8,__FUNCTION__": integrity check failed (stream length)"))
      return(result);
//--- Finalize forest conversion, clear previously allocated memory
   df.m_ForestFormat=m_DFCompressedV0;
   df.m_UseMantissa8=usemantissa8;
   df.m_Trees.Resize(0);
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| This function returns exact number of bytes required to store    |
//| compressed version of the tree starting at location TreeBase.    |
//| PARAMETERS:                                                      |
//|   DF          -  decision forest                                 |
//|   UseMantissa8-  whether 8-bit or 16-bit mantissas are used to   |
//|                  store floating point numbers                    |
//|   TreeRoot    -  root of the specific tree being stored(offset   |
//|                  in DF.Trees)                                    |
//|   TreePos     -  position within tree(first location in the tree |
//|                  is TreeRoot + 1)                                |
//|   CompressedSizes - not referenced if SaveCompressedSizes is     |
//|                  False; otherwise, values computed by this       |
//|                  function for specific values of TreePos are     |
//|                  stored to CompressedSizes[TreePos - TreeRoot]   |
//|                  (other elements of the array are not referenced)|
//|                  This array must be preallocated by caller.      |
//+------------------------------------------------------------------+
int CDForest::ComputeCompressedSizeRec(CDecisionForest &df,
                                       bool usemantissa8,
                                       int treeroot,
                                       int treepos,
                                       CRowInt &compressedsizes,
                                       bool savecompressedsizes)
  {
//--- create variables
   int result=0;
   int jmponbranch=0;
   int child0size=0;
   int child1size=0;
   int fpwidth=0;
//--- check
   if(usemantissa8)
      fpwidth=2;
   else
      fpwidth=3;
//--- Leaf or split?
   if(df.m_Trees[treepos]==-1.0)
     {
      //--- Leaf
      result=ComputeCompressedUintSize(2*df.m_NVars);
      if(df.m_NClasses==1)
         result+=fpwidth;
      else
         result+=ComputeCompressedUintSize((int)MathRound(df.m_Trees[treepos+1]));
     }
   else
     {
      //--- Split
      jmponbranch=(int)MathRound(df.m_Trees[treepos+2]);
      child0size=ComputeCompressedSizeRec(df,usemantissa8,treeroot,treepos+m_InnerNodeWidth,compressedsizes,savecompressedsizes);
      child1size=ComputeCompressedSizeRec(df,usemantissa8,treeroot,treeroot+jmponbranch,compressedsizes,savecompressedsizes);
      if(child0size<=child1size)
        {
         //--- Child #0 comes first because it is shorter
         result=ComputeCompressedUintSize((int)MathRound(df.m_Trees[treepos]));
         result+=fpwidth;
         result+=ComputeCompressedUintSize(child0size);
        }
      else
        {
         //--- Child #1 comes first because it is shorter
         result=ComputeCompressedUintSize((int)MathRound(df.m_Trees[treepos])+df.m_NVars);
         result+=fpwidth;
         result+=ComputeCompressedUintSize(child1size);
        }
      result+=child0size+child1size;
     }
//--- Do we have to save compressed sizes?
   if(savecompressedsizes)
     {
      if(!CAp::Assert(treepos-treeroot<compressedsizes.Size(),__FUNCTION__": integrity check failed"))
         return(result);
      compressedsizes.Set(treepos-treeroot,result);
     }
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| This function returns exact number of bytes required to  store   |
//| compressed version of the tree starting at location TreeBase.    |
//| PARAMETERS:                                                      |
//|   DF          -  decision forest                                 |
//|   UseMantissa8 - whether 8-bit or 16-bit mantissas are used      |
//|                  to store floating point numbers                 |
//|   TreeRoot    -  root of the specific tree being stored(offset   |
//|                  in DF.Trees)                                    |
//|   TreePos     -  position within tree (first location in the tree|
//|                  is TreeRoot + 1)                                |
//|   CompressedSizes - not referenced if SaveCompressedSizes is     |
//|                  False; otherwise, values computed by this       |
//|                  function for specific values of TreePos are     |
//|                  stored to CompressedSizes[TreePos - TreeRoot]   |
//|                  (other elements of the array are not referenced)|
//|                  This array must be preallocated by caller.      |
//+------------------------------------------------------------------+
void CDForest::CompressRec(CDecisionForest &df,
                           bool usemantissa8,
                           int treeroot,
                           int treepos,
                           CRowInt &compressedsizes,
                           CRowInt &buf,
                           int &dstoffs)
  {
//--- create variables
   int    jmponbranch=0;
   int    child0size=0;
   int    child1size=0;
   int    varidx=0;
   double leafval=0;
   double splitval=0;
   int    dstoffsold=dstoffs;
//--- Leaf or split?
   varidx=(int)MathRound(df.m_Trees[treepos]);
   if(varidx==-1)
     {
      //--- Leaf node:
      //--- * stream special value which denotes leaf (2*NVars)
      //--- * then, stream scalar value (floating point) or class number (unsigned integer)
      leafval=df.m_Trees[treepos+1];
      StreamUint(buf,dstoffs,2*df.m_NVars);
      if(df.m_NClasses==1)
         StreamFloat(buf,usemantissa8,dstoffs,leafval);
      else
         StreamUint(buf,dstoffs,(int)MathRound(leafval));
     }
   else
     {
      //--- Split node:
      //--- * fetch compressed sizes of child nodes, decide which child goes first
      jmponbranch=(int)MathRound(df.m_Trees[treepos+2]);
      splitval=df.m_Trees[treepos+1];
      child0size=compressedsizes[treepos+m_InnerNodeWidth-treeroot];
      child1size=compressedsizes[treeroot+jmponbranch-treeroot];
      if(child0size<=child1size)
        {
         //--- Child #0 comes first because it is shorter:
         //--- * stream variable index used for splitting;
         //---   value in [0,NVars) range indicates that split is
         //---   "if VAR<VAL then BRANCH0 else BRANCH1"
         //--- * stream value used for splitting
         //--- * stream children #0 and #1
         StreamUint(buf,dstoffs,varidx);
         StreamFloat(buf,usemantissa8,dstoffs,splitval);
         StreamUint(buf,dstoffs,child0size);
         CompressRec(df,usemantissa8,treeroot,treepos+m_InnerNodeWidth,compressedsizes,buf,dstoffs);
         CompressRec(df,usemantissa8,treeroot,treeroot+jmponbranch,compressedsizes,buf,dstoffs);
        }
      else
        {
         //--- Child #1 comes first because it is shorter:
         //--- * stream variable index used for splitting + NVars;
         //---   value in [NVars,2*NVars) range indicates that split is
         //---   "if VAR>=VAL then BRANCH0 else BRANCH1"
         //--- * stream value used for splitting
         //--- * stream children #0 and #1
         StreamUint(buf,dstoffs,varidx+df.m_NVars);
         StreamFloat(buf,usemantissa8,dstoffs,splitval);
         StreamUint(buf,dstoffs,child1size);
         CompressRec(df,usemantissa8,treeroot,treeroot+jmponbranch,compressedsizes,buf,dstoffs);
         CompressRec(df,usemantissa8,treeroot,treepos+m_InnerNodeWidth,compressedsizes,buf,dstoffs);
        }
     }
//--- Integrity check at the end
   if(!CAp::Assert(dstoffs-dstoffsold==compressedsizes[treepos-treeroot],__FUNCTION__": integrity check failed (compressed size at leaf)"))
      return;
  }
//+------------------------------------------------------------------+
//| This function returns exact number of bytes required to  store   |
//| compressed unsigned integer number(negative arguments result in  |
//| assertion being generated).                                      |
//+------------------------------------------------------------------+
int CDForest::ComputeCompressedUintSize(int v)
  {
//--- check
   if(!CAp::Assert(v>=0))
      return(0);
//--- create variables
   int result=1;
//---
   while(v>=128)
     {
      v=v/128;
      result=result+1;
     }
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| This function stores compressed unsigned integer number (negative|
//| arguments result in assertion being generated) to byte array at  |
//| location Offs and increments Offs by number of bytes being stored|
//+------------------------------------------------------------------+
void CDForest::StreamUint(CRowInt &buf,
                          int &offs,
                          int v)
  {
   int v0=0;
//--- check
   if(!CAp::Assert(v>=0))
      return;
//---
   while(true)
     {
      //--- Save 7 least significant bits of V, use 8th bit as a flag which
      //--- tells us whether subsequent 7-bit packages will be sent.
      v0=v%128;
      if(v>=128)
         v0=v0+128;
      buf.Set(offs,(uint)v0);
      offs++;
      v/=128;
      if(v==0)
         break;
     }
  }
//+------------------------------------------------------------------+
//| This function reads compressed unsigned integer number from byte |
//| array starting at location Offs and increments Offs by number of |
//| bytes being read.                                                |
//+------------------------------------------------------------------+
int CDForest::UnstreamUint(CRowInt &buf,int &offs)
  {
//--- create variables
   int result=0;
   int v0=0;
   int p=1;
//---
   while(true)
     {
      //--- Rad 7 bits of V, use 8th bit as a flag which tells us whether
      //--- subsequent 7-bit packages will be received.
      v0=buf[offs];
      offs=offs+1;
      result+=v0%128*p;
      if(v0<128)
         break;
      p*=128;
     }
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| This function stores compressed floating point number to byte    |
//| array at location Offs and increments Offs by number of bytes    |
//| being stored. Either 8-bit mantissa or 16-bit mantissa is used.  |
//| The exponent is always 7 bits of exponent + sign. Values which   |
//| do not fit into exponent range are truncated to fit.             |
//+------------------------------------------------------------------+
void CDForest::StreamFloat(CRowInt &buf,
                           bool usemantissa8,
                           int &offs,
                           double v)
  {
//--- create variables
   int    signbit=0;
   int    e=0;
   int    m=0;
   double twopow30=0;
   double twopowm30=0;
   double twopow10=0;
   double twopowm10=0;
//--- check
   if(!CAp::Assert(MathIsValidNumber(v),__FUNCTION__": V is not finite number"))
      return;
//--- Special case: zero
   if(v==0.0)
     {
      if(usemantissa8)
        {
         buf.Set(offs,0);
         buf.Set(offs+1,0);
         offs+=2;
        }
      else
        {
         buf.Set(offs,0);
         buf.Set(offs+1,0);
         buf.Set(offs+2,0);
         offs+=3;
        }
      return;
     }
//--- Handle sign
   signbit=0;
   if(v<0.0)
     {
      v=-v;
      signbit=128;
     }
//--- Compute exponent
   twopow30=1073741824;
   twopow10=1024;
   twopowm30=1.0/twopow30;
   twopowm10=1.0/twopow10;
   e=0;
   while(v>=twopow30)
     {
      v*=twopowm30;
      e+=30;
     }
   while(v>=twopow10)
     {
      v*=twopowm10;
      e+=10;
     }
   while(v>=1.0)
     {
      v*=0.5;
      e++;
     }
   while(v<twopowm30)
     {
      v*=twopow30;
      e-=30;
     }
   while(v<twopowm10)
     {
      v*=twopow10;
      e-=10;
     }
   while(v<0.5)
     {
      v*=2;
      e-=1;
     }
   if(!CAp::Assert(v>=0.5 && v<1.0,__FUNCTION__": integrity check failed"))
      return;
//--- Handle exponent underflow/overflow
   if(e<-63)
     {
      signbit=0;
      e=0;
      v=0;
     }
   if(e>63)
     {
      e=63;
      v=1.0;
     }
//--- Save to stream
   if(usemantissa8)
     {
      m=(int)MathRound(v*256);
      if(m==256)
        {
         m=m/2;
         e=MathMin(e+1,63);
        }
      buf.Set(offs,e+64+signbit);
      buf.Set(offs+1,m);
      offs+=2;
     }
   else
     {
      m=(int)MathRound(v*65536);
      if(m==65536)
        {
         m=m/2;
         e=MathMin(e+1,63);
        }
      buf.Set(offs,e+64+signbit);
      buf.Set(offs+1,m%256);
      buf.Set(offs+2,m/256);
      offs+=3;
     }
  }
//+------------------------------------------------------------------+
//| This function reads compressed floating point number from the    |
//| byte array starting from location Offs and increments Offs by    |
//| number of bytes being read.                                      |
//| Either 8-bit mantissa or 16-bit mantissa is used. The exponent   |
//| is always 7 bits of exponent + sign. Values which do not fit     |
//| into exponent range are truncated to fit.                        |
//+------------------------------------------------------------------+
double CDForest::UnstreamFloat(CRowInt &buf,
                               bool usemantissa8,
                               int &offs)
  {
//--- create variables
   double result=0;
   int    e=0;
   double v=0;
   double inv256=1.0/256.0;
//--- Read from stream
   if(usemantissa8)
     {
      e=buf[offs];
      v=buf[offs+1]*inv256;
      offs+=2;
     }
   else
     {
      e=buf[offs];
      v=(buf[offs+1]*inv256+buf[offs+2])*inv256;
      offs+=3 ;
     }
//--- Decode
   if(e>128)
     {
      v=-v;
      e=e-128;
     }
   e=e-64;
   result=MathPow(2.0,e)*v;
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| Classification error                                             |
//+------------------------------------------------------------------+
int CDForest::DFClsError(CDecisionForest &df,CMatrixDouble &xy,
                         const int npoints)
  {
//--- create variables
   int result=0;
   int k=0;
   int tmpi=0;
   int i_=0;
//--- creating arrays
   CRowDouble x;
   CRowDouble y;
//--- check
   if(df.m_NClasses<=1)
      return(0);
//--- initialization
   result=0;
   for(int i=0; i<npoints; i++)
     {
      //--- copy
      x=xy[i]+0;
      //--- function call
      DFProcess(df,x,y);
      //--- change values
      k=(int)MathRound(xy.Get(i,df.m_NVars));
      tmpi=0;
      for(int j=1; j<df.m_NClasses; j++)
        {
         //--- check
         if(y[j]>y[tmpi])
            tmpi=j;
        }
      //--- check
      if(tmpi!=k)
         result++;
     }
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| Internal subroutine for processing one decision tree stored in   |
//| uncompressed format starting at SubtreeRoot (this index points   |
//| to the header of the tree, not its first node). First node being |
//| processed is located at NodeOffs.                                |
//+------------------------------------------------------------------+
void CDForest::DFProcessInternalUncompressed(CDecisionForest &df,
                                             int subtreeroot,
                                             int nodeoffs,
                                             CRowDouble &x,
                                             CRowDouble &y)
  {
   int idx=0;
//--- check
   if(!CAp::Assert(df.m_ForestFormat==m_DFUncompressedV0,__FUNCTION__+": unexpected forest format"))
      return;
//--- Navigate through the tree
   while(true)
     {
      if(df.m_Trees[nodeoffs]==(-1.0))
        {
         if(df.m_NClasses==1)
            y.Add(0,df.m_Trees[nodeoffs+1]);
         else
           {
            idx=(int)MathRound(df.m_Trees[nodeoffs+1]);
            y.Add(idx,1);
           }
         break;
        }
      if(x[(int)MathRound(df.m_Trees[nodeoffs])]<df.m_Trees[nodeoffs+1])
         nodeoffs+=m_InnerNodeWidth;
      else
         nodeoffs=subtreeroot+(int)MathRound(df.m_Trees[nodeoffs+2]);
     }
  }
//+------------------------------------------------------------------+
//| Internal subroutine for processing one decision tree stored in   |
//| compressed format starting at Offs (this index points to the     |
//| first node of the tree, right past the header field).            |
//+------------------------------------------------------------------+
void CDForest::DFProcessInternalCompressed(CDecisionForest &df,
                                           int offs,
                                           CRowDouble &x,
                                           CRowDouble &y)
  {
//--- create variables
   int    leafindicator=0;
   int    varidx=0;
   double splitval=0;
   int    jmplen=0;
   double leafval=0;
   int    leafcls=0;
//--- check
   if(!CAp::Assert(df.m_ForestFormat==m_DFCompressedV0,__FUNCTION__+": unexpected forest format"))
      return;
//--- Navigate through the tree
   leafindicator=2*df.m_NVars;
   while(true)
     {
      //--- Read variable idx
      varidx=UnstreamUint(df.m_Trees8,offs);
      //--- Is it leaf?
      if(varidx==leafindicator)
        {
         if(df.m_NClasses==1)
           {
            //--- Regression forest
            leafval=UnstreamFloat(df.m_Trees8,df.m_UseMantissa8,offs);
            y.Add(0,leafval);
           }
         else
           {
            //--- Classification forest
            leafcls=UnstreamUint(df.m_Trees8,offs);
            y.Add(leafcls,1);
           }
         break;
        }
      //--- Process node
      splitval=UnstreamFloat(df.m_Trees8,df.m_UseMantissa8,offs);
      jmplen=UnstreamUint(df.m_Trees8,offs);
      if(varidx<df.m_NVars)
        {
         //--- The split rule is "if VAR<VAL then BRANCH0 else BRANCH1"
         if(x[varidx]>=splitval)
            offs+=jmplen;
        }
      else
        {
         //--- The split rule is "if VAR>=VAL then BRANCH0 else BRANCH1"
         varidx-=df.m_NVars;
         if(x[varidx]<splitval)
            offs+=jmplen;
        }
     }
  }
//+------------------------------------------------------------------+
//| Middle and clusterization                                        |
//+------------------------------------------------------------------+
class CKMeans
  {
public:
   static void       KMeansGenerate(CMatrixDouble &xy,const int npoints,const int nvars,const int k,const int restarts,int &info,CMatrixDouble &c,int &xyc[]);

private:
   static bool       SelectCenterPP(CMatrixDouble &xy,const int npoints,const int nvars,CMatrixDouble &centers,bool &cbusycenters[],const int ccnt,double &d2[],double &p[],double &tmp[]);
  };
//+------------------------------------------------------------------+
//| k-means++ clusterization                                         |
//| INPUT PARAMETERS:                                                |
//|     XY          -   dataset, array [0..NPoints-1,0..NVars-1].    |
//|     NPoints     -   dataset size, NPoints>=K                     |
//|     NVars       -   number of variables, NVars>=1                |
//|     K           -   desired number of clusters, K>=1             |
//|     Restarts    -   number of restarts, Restarts>=1              |
//| OUTPUT PARAMETERS:                                               |
//|     Info        -   return code:                                 |
//|                     * -3, if task is degenerate (number of       |
//|                           distinct points is less than K)        |
//|                     * -1, if incorrect                           |
//|                           NPoints/NFeatures/K/Restarts was passed|
//|                     *  1, if subroutine finished successfully    |
//|     C           -   array[0..NVars-1,0..K-1].matrix whose columns|
//|                     store cluster's centers                      |
//|     XYC         -   array[NPoints], which contains cluster       |
//|                     indexes                                      |
//+------------------------------------------------------------------+
void CKMeans::KMeansGenerate(CMatrixDouble &xy,const int npoints,
                             const int nvars,const int k,
                             const int restarts,int &info,
                             CMatrixDouble &c,int &xyc[])
  {
//--- create variables
   int    i=0;
   int    j=0;
   double e=0;
   double ebest=0;
   double v=0;
   int    cclosest=0;
   bool   waschanges;
   bool   zerosizeclusters;
   int    pass=0;
   int    i_=0;
   double dclosest=0;
//--- creating arrays
   int    xycbest[];
   double x[];
   double tmp[];
   double d2[];
   double p[];
   int    csizes[];
   bool   cbusy[];
   double work[];
//--- create matrix
   CMatrixDouble ct;
   CMatrixDouble ctbest;
//--- initialization
   info=0;
//--- Test parameters
   if(npoints<k || nvars<1 || k<1 || restarts<1)
     {
      info=-1;
      return;
     }
//--- TODO: special case K=1
//--- TODO: special case K=NPoints
   info=1;
//--- Multiple passes of k-means++ algorithm
   ct.Resize(k,nvars);
   ctbest.Resize(k,nvars);
   ArrayResizeAL(xyc,npoints);
   ArrayResizeAL(xycbest,npoints);
   ArrayResize(d2,npoints);
   ArrayResize(p,npoints);
   ArrayResize(tmp,nvars);
   ArrayResizeAL(csizes,k);
   ArrayResizeAL(cbusy,k);
//--- change value
   ebest=CMath::m_maxrealnumber;
//--- calculation
   for(pass=1; pass<=restarts; pass++)
     {
      //--- Select initial centers  using k-means++ algorithm
      //--- 1. Choose first center at random
      //--- 2. Choose next centers using their distance from centers already chosen
      //--- Note that for performance reasons centers are stored in ROWS of CT,not
      //--- in columns. We'll transpose CT in the end and store it in the C.
      i=CMath::RandomInteger(npoints);
      for(i_=0; i_<=nvars-1; i_++)
         ct.Set(0,i_,xy[i][i_]);
      cbusy[0]=true;
      for(i=1; i<=k-1; i++)
         cbusy[i]=false;
      //--- check
      if(!SelectCenterPP(xy,npoints,nvars,ct,cbusy,k,d2,p,tmp))
        {
         info=-3;
         return;
        }
      //--- Update centers:
      //--- 2. update center positions
      for(i=0; i<npoints; i++)
         xyc[i]=-1;
      //--- cycle
      while(true)
        {
         //--- fill XYC with center numbers
         waschanges=false;
         for(i=0; i<npoints; i++)
           {
            //--- change values
            cclosest=-1;
            dclosest=CMath::m_maxrealnumber;
            for(j=0; j<=k-1; j++)
              {
               //--- calculation
               for(i_=0; i_<=nvars-1; i_++)
                  tmp[i_]=xy[i][i_];
               for(i_=0; i_<=nvars-1; i_++)
                  tmp[i_]=tmp[i_]-ct[j][i_];
               v=0.0;
               for(i_=0; i_<=nvars-1; i_++)
                  v+=tmp[i_]*tmp[i_];
               //--- check
               if(v<dclosest)
                 {
                  cclosest=j;
                  dclosest=v;
                 }
              }
            //--- check
            if(xyc[i]!=cclosest)
               waschanges=true;
            //--- change value
            xyc[i]=cclosest;
           }
         //--- Update centers
         for(j=0; j<=k-1; j++)
            csizes[j]=0;
         for(i=0; i<=k-1; i++)
           {
            for(j=0; j<=nvars-1; j++)
               ct.Set(i,j,0);
           }
         //--- change values
         for(i=0; i<npoints; i++)
           {
            csizes[xyc[i]]=csizes[xyc[i]]+1;
            for(i_=0; i_<=nvars-1; i_++)
               ct.Set(xyc[i],i_,ct[xyc[i]][i_]+xy[i][i_]);
           }
         zerosizeclusters=false;
         for(i=0; i<=k-1; i++)
           {
            cbusy[i]=csizes[i]!=0;
            zerosizeclusters=zerosizeclusters || csizes[i]==0;
           }
         //--- check
         if(zerosizeclusters)
           {
            //--- Some clusters have zero size - rare,but possible.
            //--- We'll choose new centers for such clusters using k-means++ rule
            //--- and restart algorithm
            if(!SelectCenterPP(xy,npoints,nvars,ct,cbusy,k,d2,p,tmp))
              {
               info=-3;
               return;
              }
            continue;
           }
         //--- copy
         for(j=0; j<=k-1; j++)
           {
            v=1.0/(double)csizes[j];
            for(i_=0; i_<=nvars-1; i_++)
               ct.Set(j,i_,v*ct[j][i_]);
           }
         //--- if nothing has changed during iteration
         if(!waschanges)
            break;
        }
      //--- 3. Calculate E,compare with best centers found so far
      e=0;
      for(i=0; i<npoints; i++)
        {
         for(i_=0; i_<=nvars-1; i_++)
            tmp[i_]=xy[i][i_];
         for(i_=0; i_<=nvars-1; i_++)
            tmp[i_]=tmp[i_]-ct[xyc[i]][i_];
         //--- calculation
         v=0.0;
         for(i_=0; i_<=nvars-1; i_++)
            v+=tmp[i_]*tmp[i_];
         e=e+v;
        }
      //--- check
      if(e<ebest)
        {
         //--- store partition.
         ebest=e;
         //--- function call
         CBlas::CopyMatrix(ct,0,k-1,0,nvars-1,ctbest,0,k-1,0,nvars-1);
         //--- copy
         for(i=0; i<npoints; i++)
            xycbest[i]=xyc[i];
        }
     }
//--- Copy and transpose
   c.Resize(nvars,k);
//--- function call
   CBlas::CopyAndTranspose(ctbest,0,k-1,0,nvars-1,c,0,nvars-1,0,k-1);
//--- copy
   for(i=0; i<npoints; i++)
      xyc[i]=xycbest[i];
  }
//+------------------------------------------------------------------+
//| Select center for a new cluster using k-means++ rule             |
//+------------------------------------------------------------------+
bool CKMeans::SelectCenterPP(CMatrixDouble &xy,const int npoints,
                             const int nvars,CMatrixDouble &centers,
                             bool &cbusycenters[],const int ccnt,
                             double &d2[],double &p[],double &tmp[])
  {
//--- create variables
   bool   result;
   int    i=0;
   int    j=0;
   int    cc=0;
   double v=0;
   double s=0;
   int    i_=0;
//--- create array
   double busycenters[];
//--- copy
   ArrayCopy(busycenters,cbusycenters);
//--- initialization
   result=true;
//--- calculation
   for(cc=0; cc<=ccnt-1; cc++)
     {
      //--- check
      if(!busycenters[cc])
        {
         //--- fill D2
         for(i=0; i<npoints; i++)
           {
            d2[i]=CMath::m_maxrealnumber;
            for(j=0; j<=ccnt-1; j++)
              {
               //--- check
               if(busycenters[j])
                 {
                  for(i_=0; i_<=nvars-1; i_++)
                     tmp[i_]=xy[i][i_];
                  for(i_=0; i_<=nvars-1; i_++)
                     tmp[i_]=tmp[i_]-centers[j][i_];
                  //--- calculation
                  v=0.0;
                  for(i_=0; i_<=nvars-1; i_++)
                     v+=tmp[i_]*tmp[i_];
                  //--- check
                  if(v<d2[i])
                     d2[i]=v;
                 }
              }
           }
         //--- calculate P (non-cumulative)
         s=0;
         for(i=0; i<npoints; i++)
            s=s+d2[i];
         //--- check
         if(s==0.0)
            return(false);
         //--- change value
         s=1/s;
         for(i_=0; i_<npoints; i_++)
            p[i_]=s*d2[i_];
         //--- choose one of points with probability P
         //--- random number within (0,1) is generated and
         //--- inverse empirical CDF is used to randomly choose a point.
         s=0;
         v=CMath::RandomReal();
         //--- calculation
         for(i=0; i<npoints; i++)
           {
            s=s+p[i];
            //--- check
            if(v<=s || i==npoints-1)
              {
               for(i_=0; i_<=nvars-1; i_++)
                  centers.Set(cc,i_,xy[i][i_]);
               busycenters[cc]=true;
               //--- break the cycle
               break;
              }
           }
        }
     }
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| Multiclass Fisher LDA                                            |
//+------------------------------------------------------------------+
class CLDA
  {
public:
   static void       FisherLDA(CMatrixDouble &xy,const int npoints,const int nvars,const int nclasses,int &info,double &w[]);
   static void       FisherLDA(CMatrixDouble &xy,const int npoints,const int nvars,const int nclasses,int &info,CRowDouble &w);
   static void       FisherLDAN(CMatrixDouble &xy,const int npoints,const int nvars,const int nclasses,int &info,CMatrixDouble &w);
  };
//+------------------------------------------------------------------+
//| Multiclass Fisher LDA                                            |
//| Subroutine finds coefficients of linear combination which        |
//| optimally separates training set on classes.                     |
//| INPUT PARAMETERS:                                                |
//|     XY          -   training set, array[0..NPoints-1,0..NVars].  |
//|                     First NVars columns store values of          |
//|                     independent variables, next column stores    |
//|                     number of class (from 0 to NClasses-1) which |
//|                     dataset element belongs to. Fractional values|
//|                     are rounded to nearest integer.              |
//|     NPoints     -   training set size, NPoints>=0                |
//|     NVars       -   number of independent variables, NVars>=1    |
//|     NClasses    -   number of classes, NClasses>=2               |
//| OUTPUT PARAMETERS:                                               |
//|     Info        -   return code:                                 |
//|                     * -4, if internal EVD subroutine hasn't      |
//|                           converged                              |
//|                     * -2, if there is a point with class number  |
//|                           outside of [0..NClasses-1].            |
//|                     * -1, if incorrect parameters was passed     |
//|                           (NPoints<0, NVars<1, NClasses<2)       |
//|                     *  1, if task has been solved                |
//|                     *  2, if there was a multicollinearity in    |
//|                           training set, but task has been solved.|
//|     W           -   linear combination coefficients,             |
//|                     array[0..NVars-1]                            |
//+------------------------------------------------------------------+
void CLDA::FisherLDA(CMatrixDouble &xy,const int npoints,
                     const int nvars,const int nclasses,
                     int &info,double &w[])
  {
   CRowDouble W;
   FisherLDA(xy,npoints,nvars,nclasses,info,W);
   W.ToArray(w);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CLDA::FisherLDA(CMatrixDouble &xy,const int npoints,
                     const int nvars,const int nclasses,
                     int &info,CRowDouble &w)
  {
//--- create matrix
   CMatrixDouble w2;
//--- initialization
   info=0;
//--- function call
   FisherLDAN(xy,npoints,nvars,nclasses,info,w2);
//--- check and copy
   if(info>0)
      w=w2.Col(0)+0;
  }
//+------------------------------------------------------------------+
//| N-dimensional multiclass Fisher LDA                              |
//| Subroutine finds coefficients of linear combinations which       |
//| optimally separates                                              |
//| training set on classes. It returns N-dimensional basis whose    |
//| vector are sorted                                                |
//| by quality of training set separation (in descending order).     |
//| INPUT PARAMETERS:                                                |
//|     XY          -   training set, array[0..NPoints-1,0..NVars].  |
//|                     First NVars columns store values of          |
//|                     independent variables, next column stores    |
//|                     number of class (from 0 to NClasses-1) which |
//|                     dataset element belongs to. Fractional values|
//|                     are rounded to nearest integer.              |
//|     NPoints     -   training set size, NPoints>=0                |
//|     NVars       -   number of independent variables, NVars>=1    |
//|     NClasses    -   number of classes, NClasses>=2               |
//| OUTPUT PARAMETERS:                                               |
//|     Info        -   return code:                                 |
//|                     * -4, if internal EVD subroutine hasn't      |
//|                           converged                              |
//|                     * -2, if there is a point with class number  |
//|                           outside of [0..NClasses-1].            |
//|                     * -1, if incorrect parameters was passed     |
//|                           (NPoints<0, NVars<1, NClasses<2)       |
//|                     *  1, if task has been solved                |
//|                     *  2, if there was a multicollinearity in    |
//|                           training set, but task has been solved.|
//|     W           -   basis, array[0..NVars-1,0..NVars-1]          |
//|                     columns of matrix stores basis vectors,      |
//|                     sorted by quality of training set separation |
//|                     (in descending order)                        |
//+------------------------------------------------------------------+
void CLDA::FisherLDAN(CMatrixDouble &xy,const int npoints,const int nvars,
                      const int nclasses,int &info,CMatrixDouble &w)
  {
//--- create variables
   int    i=0;
   int    j=0;
   int    k=0;
   int    m=0;
   double v=0;
   int    i_=0;
//--- creating arrays
   int    c[];
   vector<double> mu;
   int    nc[];
   vector<double> tf;
   CRowDouble d;
   CRowDouble d2;
   CRowDouble work;
//--- create matrix
   CMatrixDouble muc;
   CMatrixDouble sw;
   CMatrixDouble st;
   CMatrixDouble z;
   CMatrixDouble z2;
   CMatrixDouble tm;
   CMatrixDouble sbroot;
   CMatrixDouble a;
   CMatrixDouble xyc;
   CMatrixDouble xyproj;
   CMatrixDouble wproj;
//--- initialization
   info=0;
//--- Test data
   if((npoints<0 || nvars<1) || nclasses<2)
     {
      info=-1;
      return;
     }
   mu=xy.Col(nvars);
   mu.Resize(npoints);
//--- check
   if((int)MathRound(mu.Min())<0 || (int)MathRound(mu.Max())>=nclasses)
     {
      info=-2;
      return;
     }
//--- change value
   info=1;
//--- Special case: NPoints<=1
//--- Degenerate task.
   if(npoints<=1)
     {
      info=2;
      //--- initialization
      w=matrix<double>::Identity(nvars,nvars);
      //--- exit the function
      return;
     }
//--- Prepare temporaries
   tf.Resize(nvars);
   work.Resize(MathMax(nvars,npoints)+1);
//--- Convert class labels from reals to integers (just for convenience)
   ArrayResizeAL(c,npoints);
   for(i=0; i<npoints; i++)
      c[i]=(int)MathRound(xy.Get(i,nvars));
//--- Calculate class sizes and means
   mu=xy.Sum(0);
   mu.Resize(nvars);
   muc=matrix<double>::Zeros(nclasses,nvars+1);
   ArrayResize(nc,nclasses);
   ArrayInitialize(nc,0);
//--- calculation
   for(i=0; i<npoints; i++)
     {
      for(i_=0; i_<nvars; i_++)
         muc.Add(c[i],i_,xy.Get(i,i_));
      nc[c[i]]++;
     }
   muc.Resize(nclasses,nvars);
   for(i=0; i<nclasses; i++)
      muc.Row(i,muc[i]/(double)nc[i]);
//--- change values
   mu=mu/(double)npoints;
//--- Create ST matrix
   xyc=xy.Transpose()+0;
   xyc.Resize(nvars,npoints);
   for(k=0; k<nvars; k++)
      xyc.Row(k,xyc[k]-mu[k]);
   st=xyc.MatMul(xyc,true)+0;
//--- Create SW matrix
   xyc=xy.Transpose()+0;
   xyc.Resize(nvars,npoints);
   for(k=0; k<npoints; k++)
      xyc.Col(k,xyc.Col(k)-muc[c[k]]);
   sw=xyc.MatMul(xyc,true)+0;
//--- Maximize ratio J=(w'*ST*w)/(w'*SW*w).
//--- First,make transition from w to v such that w'*ST*w becomes v'*v:
//---    v=root(ST)*w=R*w
//---    R=root(D)*Z'
//---    w=(root(ST)^-1)*v=RI*v
//---    RI=Z*inv(root(D))
//---    J=(v'*v)/(v'*(RI'*SW*RI)*v)
//---    ST=Z*D*Z'
//---    so we have
//---    J=(v'*v) / (v'*(inv(root(D))*Z'*SW*Z*inv(root(D)))*v)=
//=(v'*v) / (v'*A*v)
   if(!CEigenVDetect::SMatrixEVD(st,nvars,1,true,d,z))
     {
      info=-4;
      return;
     }
//--- allocation
   w.Resize(nvars,nvars);
//--- check
   if(d[nvars-1]<=0.0 || d[0]<=1000*CMath::m_machineepsilon*d[nvars-1])
     {
      //--- Special case: D[NVars-1]<=0
      //--- Degenerate task (all variables takes the same value).
      if(d[nvars-1]<=0.0)
        {
         info=2;
         w=matrix<double>::Identity(nvars,nvars);
         //--- exit the function
         return;
        }
      //--- Special case: degenerate ST matrix,multicollinearity found.
      //--- Since we know ST eigenvalues/vectors we can translate task to
      //--- non-degenerate form.
      //--- Let WG is orthogonal basis of the non zero variance subspace
      //--- of the ST and let WZ is orthogonal basis of the zero variance
      //--- subspace.
      //--- Projection on WG allows us to use LDA on reduced M-dimensional
      //--- subspace,N-M vectors of WZ allows us to update reduced LDA
      //--- factors to full N-dimensional subspace.
      m=0;
      for(k=0; k<nvars; k++)
        {
         //--- check
         if(d[k]<=1000*CMath::m_machineepsilon*d[nvars-1])
            m=k+1;
        }
      //--- check
      if(!CAp::Assert(m!=0,__FUNCTION__+": internal error #1"))
         return;
      //--- allocation
      xyproj.Resize(npoints,nvars-m+1);
      //--- function call
      CBlas::MatrixMatrixMultiply(xy,0,npoints-1,0,nvars-1,false,z,0,nvars-1,m,nvars-1,false,1.0,xyproj,0,npoints-1,0,nvars-m-1,0.0,work);
      for(i=0; i<npoints; i++)
         xyproj.Set(i,nvars-m,xy.Get(i,nvars));
      //--- function call
      FisherLDAN(xyproj,npoints,nvars-m,nclasses,info,wproj);
      //--- check
      if(info<0)
         return;
      //--- function call
      CBlas::MatrixMatrixMultiply(z,0,nvars-1,m,nvars-1,false,wproj,0,nvars-m-1,0,nvars-m-1,false,1.0,w,0,nvars-1,0,nvars-m-1,0.0,work);
      //--- change values
      for(k=nvars-m; k<nvars; k++)
         w.Col(k,z.Col(k-nvars+m)+0);
      info=2;
     }
   else
     {
      //--- General case: no multicollinearity
      tm.Resize(nvars,nvars);
      a.Resize(nvars,nvars);
      //--- function call
      tm=sw.MatMul(z,false)+0;
      a=z.Transpose()+0;
      a=a.MatMul(tm,false)+0;
      //--- change values
      for(i=0; i<nvars; i++)
         for(j=0; j<nvars; j++)
            a.Mul(i,j,1.0/MathSqrt(d[i]*d[j]));
      //--- check
      if(!CEigenVDetect::SMatrixEVD(a,nvars,1,true,d2,z2))
        {
         info=-4;
         return;
        }
      //--- calculation
      for(k=0; k<nvars; k++)
         z2.Row(k,z2[k]/MathSqrt(d[k]));
      w=z.MatMul(z2,false)+0;
     }
//--- Post-processing:
//--- * normalization
//--- * converting to non-negative form,if possible
   for(k=0; k<nvars; k++)
     {
      //--- calculation
      v=MathPow(w.Col(k)+0,2.0).Sum();
      w.Col(k,w.Col(k)/MathSqrt(v));
      v=(w.Col(k)+0).Sum();
      //--- check
      if(v<0.0)
         w.Col(k,w.Col(k)*(-1.0));
     }
  }
//+------------------------------------------------------------------+
//| Auxiliary class for CLinReg                                      |
//+------------------------------------------------------------------+
class CLinearModel
  {
public:
   CRowDouble        m_w;
   //--- constructor, destructor
                     CLinearModel(void) {}
                    ~CLinearModel(void) {}
   //--- copy
   void              Copy(const CLinearModel &obj) { m_w=obj.m_w; }
   //--- overloading
   void              operator=(const CLinearModel &obj) { Copy(obj); }

  };
//+------------------------------------------------------------------+
//| This class is a shell for class CLinearModel                     |
//+------------------------------------------------------------------+
class CLinearModelShell
  {
private:
   CLinearModel      m_innerobj;

public:
   //--- constructors, destructor
                     CLinearModelShell(void) {}
                     CLinearModelShell(CLinearModel &obj) { m_innerobj.Copy(obj); }
                    ~CLinearModelShell(void) {}
   //--- method
   CLinearModel     *GetInnerObj(void) { return(GetPointer(m_innerobj)); }
  };
//+------------------------------------------------------------------+
//| LRReport structure contains additional information about linear  |
//| model:                                                           |
//| * C             -   covariation matrix, array[0..NVars,0..NVars].|
//|                     C[i,j] = Cov(A[i],A[j])                      |
//| * RMSError      -   root mean square error on a training set     |
//| * AvgError      -   average error on a training set              |
//| * AvgRelError   -   average relative error on a training set     |
//|                     (excluding observations with zero function   |
//|                     value).                                      |
//| * CVRMSError    -   leave-one-out cross-validation estimate of   |
//|                     generalization error. Calculated using fast  |
//|                     algorithm with O(NVars*NPoints) complexity.  |
//| * CVAvgError    -   cross-validation estimate of average error   |
//| * CVAvgRelError -   cross-validation estimate of average relative|
//|                     error                                        |
//| All other fields of the structure are intended for internal use  |
//| and should not be used outside ALGLIB.                           |
//+------------------------------------------------------------------+
class CLRReport
  {
public:
   //--- variables
   double            m_RMSError;
   double            m_AvgError;
   double            m_AvgRelError;
   double            m_cvrmserror;
   double            m_cvavgerror;
   double            m_cvavgrelerror;
   int               m_ncvdefects;
   //--- array
   CRowInt           m_cvdefects;
   //--- matrix
   CMatrixDouble     m_c;
   //--- constructor, destructor
                     CLRReport(void);
                    ~CLRReport(void) {}
   //--- copy
   void              Copy(const CLRReport &obj);
   //--- overloading
   void              operator=(const CLRReport &obj) { Copy(obj); }

  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CLRReport::CLRReport(void)
  {
//--- copy variables
   m_RMSError=0;
   m_AvgError=0;
   m_AvgRelError=0;
   m_cvrmserror=0;
   m_cvavgerror=0;
   m_cvavgrelerror=0;
   m_ncvdefects=0;
  }
//+------------------------------------------------------------------+
//| Copy                                                             |
//+------------------------------------------------------------------+
void CLRReport::Copy(const CLRReport &obj)
  {
//--- copy variables
   m_RMSError=obj.m_RMSError;
   m_AvgError=obj.m_AvgError;
   m_AvgRelError=obj.m_AvgRelError;
   m_cvrmserror=obj.m_cvrmserror;
   m_cvavgerror=obj.m_cvavgerror;
   m_cvavgrelerror=obj.m_cvavgrelerror;
   m_ncvdefects=obj.m_ncvdefects;
//--- copy array
   m_cvdefects=obj.m_cvdefects;
//--- copy matrix
   m_c=obj.m_c;
  }
//+------------------------------------------------------------------+
//| LRReport structure contains additional information about linear  |
//| model:                                                           |
//| * C             -   covariation matrix, array[0..NVars,0..NVars].|
//|                     C[i,j]=Cov(A[i],A[j])                        |
//| * RMSError      -   root mean square error on a training set     |
//| * AvgError      -   average error on a training set              |
//| * AvgRelError   -   average relative error on a training set     |
//|                     (excluding observations with zero function   |
//|                     value).                                      |
//| * CVRMSError    -   leave-one-out cross-validation estimate of   |
//|                     generalization error. Calculated using fast  |
//|                     algorithm with O(NVars*NPoints) complexity.  |
//| * CVAvgError    -   cross-validation estimate of average error   |
//| * CVAvgRelError -   cross-validation estimate of average relative|
//|                     error                                        |
//| All other fields of the structure are intended for internal use  |
//| and should not be used outside ALGLIB.                           |
//+------------------------------------------------------------------+
class CLRReportShell
  {
private:
   CLRReport         m_innerobj;

public:
   //--- constructors, destructor
                     CLRReportShell(void) {}
                     CLRReportShell(CLRReport &obj) { m_innerobj.Copy(obj); }
                    ~CLRReportShell(void) {}
   //--- methods
   double            GetRMSError(void);
   void              SetRMSError(const double d);
   double            GetAvgError(void);
   void              SetAvgError(const double d);
   double            GetAvgRelError(void);
   void              SetAvgRelError(const double d);
   double            GetCVRMSError(void);
   void              SetCVRMSError(const double d);
   double            GetCVAvgError(void);
   void              SetCVAvgError(const double d);
   double            GetCVAvgRelError(void);
   void              SetCVAvgRelError(const double d);
   int               GetNCVDEfects(void);
   void              SetNCVDEfects(const int i);
   CLRReport        *GetInnerObj(void);
  };
//+------------------------------------------------------------------+
//| Returns the value of the variable rmserror                       |
//+------------------------------------------------------------------+
double CLRReportShell::GetRMSError(void)
  {
   return(m_innerobj.m_RMSError);
  }
//+------------------------------------------------------------------+
//| Changing the value of the variable rmserror                      |
//+------------------------------------------------------------------+
void CLRReportShell::SetRMSError(const double d)
  {
   m_innerobj.m_RMSError=d;
  }
//+------------------------------------------------------------------+
//| Returns the value of the variable avgerror                       |
//+------------------------------------------------------------------+
double CLRReportShell::GetAvgError(void)
  {
   return(m_innerobj.m_AvgError);
  }
//+------------------------------------------------------------------+
//| Changing the value of the variable avgerror                      |
//+------------------------------------------------------------------+
void CLRReportShell::SetAvgError(const double d)
  {
   m_innerobj.m_AvgError=d;
  }
//+------------------------------------------------------------------+
//| Returns the value of the variable avgrelerror                    |
//+------------------------------------------------------------------+
double CLRReportShell::GetAvgRelError(void)
  {
   return(m_innerobj.m_AvgRelError);
  }
//+------------------------------------------------------------------+
//| Changing the value of the variable avgrelerror                   |
//+------------------------------------------------------------------+
void CLRReportShell::SetAvgRelError(const double d)
  {
   m_innerobj.m_AvgRelError=d;
  }
//+------------------------------------------------------------------+
//| Returns the value of the variable cvrmserror                     |
//+------------------------------------------------------------------+
double CLRReportShell::GetCVRMSError(void)
  {
   return(m_innerobj.m_cvrmserror);
  }
//+------------------------------------------------------------------+
//| Changing the value of the variable cvrmserror                    |
//+------------------------------------------------------------------+
void CLRReportShell::SetCVRMSError(const double d)
  {
   m_innerobj.m_cvrmserror=d;
  }
//+------------------------------------------------------------------+
//| Returns the value of the variable cvavgerror                     |
//+------------------------------------------------------------------+
double CLRReportShell::GetCVAvgError(void)
  {
   return(m_innerobj.m_cvavgerror);
  }
//+------------------------------------------------------------------+
//| Changing the value of the variable cvavgerror                    |
//+------------------------------------------------------------------+
void CLRReportShell::SetCVAvgError(const double d)
  {
   m_innerobj.m_cvavgerror=d;
  }
//+------------------------------------------------------------------+
//| Returns the value of the variable cvavgrelerror                  |
//+------------------------------------------------------------------+
double CLRReportShell::GetCVAvgRelError(void)
  {
   return(m_innerobj.m_cvavgrelerror);
  }
//+------------------------------------------------------------------+
//| Changing the value of the variable cvavgrelerror                 |
//+------------------------------------------------------------------+
void CLRReportShell::SetCVAvgRelError(const double d)
  {
   m_innerobj.m_cvavgrelerror=d;
  }
//+------------------------------------------------------------------+
//| Returns the value of the variable ncvdefects                     |
//+------------------------------------------------------------------+
int CLRReportShell::GetNCVDEfects(void)
  {
   return(m_innerobj.m_ncvdefects);
  }
//+------------------------------------------------------------------+
//| Changing the value of the variable ncvdefects                    |
//+------------------------------------------------------------------+
void CLRReportShell::SetNCVDEfects(const int i)
  {
   m_innerobj.m_ncvdefects=i;
  }
//+------------------------------------------------------------------+
//| Return object of class                                           |
//+------------------------------------------------------------------+
CLRReport *CLRReportShell::GetInnerObj(void)
  {
   return(GetPointer(m_innerobj));
  }
//+------------------------------------------------------------------+
//| Linear regression class                                          |
//+------------------------------------------------------------------+
class CLinReg
  {
public:
   //--- constant
   static const int  m_lrvnum;
   //--- public methods
   static void       LRBuild(CMatrixDouble &xy,const int npoints,const int nvars,int &info,CLinearModel &lm,CLRReport &ar);
   static void       LRBuildS(CMatrixDouble &xy,double &s[],const int npoints,const int nvars,int &info,CLinearModel &lm,CLRReport &ar);
   static void       LRBuildS(CMatrixDouble &xy,CRowDouble &s,const int npoints,const int nvars,int &info,CLinearModel &lm,CLRReport &ar);
   static void       LRBuildZS(CMatrixDouble &xy,double &s[],const int npoints,const int nvars,int &info,CLinearModel &lm,CLRReport &ar);
   static void       LRBuildZS(CMatrixDouble &xy,CRowDouble &s,const int npoints,const int nvars,int &info,CLinearModel &lm,CLRReport &ar);
   static void       LRBuildZ(CMatrixDouble &xy,const int npoints,const int nvars,int &info,CLinearModel &lm,CLRReport &ar);
   static void       LRUnpack(CLinearModel &lm,double &v[],int &nvars);
   static void       LRUnpack(CLinearModel &lm,CRowDouble &v,int &nvars);
   static void       LRPack(double &v[],const int nvars,CLinearModel &lm);
   static void       LRPack(CRowDouble &v,const int nvars,CLinearModel &lm);
   static double     LRProcess(CLinearModel &lm,double &x[]);
   static double     LRProcess(CLinearModel &lm,CRowDouble &x);
   static double     LRRMSError(CLinearModel &lm,CMatrixDouble &xy,const int npoints);
   static double     LRAvgError(CLinearModel &lm,CMatrixDouble &xy,const int npoints);
   static double     LRAvgRelError(CLinearModel &lm,CMatrixDouble &xy,const int npoints);
   static void       LRCopy(CLinearModel &lm1,CLinearModel &lm2);
   static void       LRLines(CMatrixDouble &xy,double &s[],const int n,int &info,double &a,double &b,double &vara,double &varb,double &covab,double &corrab,double &p);
   static void       LRLines(CMatrixDouble &xy,CRowDouble &s,const int n,int &info,double &a,double &b,double &vara,double &varb,double &covab,double &corrab,double &p);
   static void       LRLine(CMatrixDouble &xy,const int n,int &info,double &a,double &b);

private:
   static void       LRInternal(CMatrixDouble &xy,CRowDouble &s,const int npoints,const int nvars,int &info,CLinearModel &lm,CLRReport &ar);
  };
//+------------------------------------------------------------------+
//| Initialize constant                                              |
//+------------------------------------------------------------------+
const int CLinReg::m_lrvnum=5;
//+------------------------------------------------------------------+
//| Linear regression                                                |
//| Subroutine builds model:                                         |
//|     Y = A(0)*X[0] + ... + A(N-1)*X[N-1] + A(N)                   |
//| and model found in ALGLIB format, covariation matrix, training   |
//| set errors (rms, average, average relative) and leave-one-out    |
//| cross-validation estimate of the generalization error. CV        |
//| estimate calculated using fast algorithm with O(NPoints*NVars)   |
//| complexity.                                                      |
//| When  covariation  matrix  is  calculated  standard deviations of|
//| function values are assumed to be equal to RMS error on the      |
//| training set.                                                    |
//| INPUT PARAMETERS:                                                |
//|     XY          -   training set, array [0..NPoints-1,0..NVars]: |
//|                     * NVars columns - independent variables      |
//|                     * last column - dependent variable           |
//|     NPoints     -   training set size, NPoints>NVars+1           |
//|     NVars       -   number of independent variables              |
//| OUTPUT PARAMETERS:                                               |
//|     Info        -   return code:                                 |
//|                     * -255, in case of unknown internal error    |
//|                     * -4, if internal SVD subroutine haven't     |
//|                           converged                              |
//|                     * -1, if incorrect parameters was passed     |
//|                           (NPoints<NVars+2, NVars<1).            |
//|                     *  1, if subroutine successfully finished    |
//|     LM          -   linear model in the ALGLIB format. Use       |
//|                     subroutines of this unit to work with the    |
//|                     model.                                       |
//|     AR          -   additional results                           |
//+------------------------------------------------------------------+
void CLinReg::LRBuild(CMatrixDouble &xy,const int npoints,const int nvars,
                      int &info,CLinearModel &lm,CLRReport &ar)
  {
//--- initialization
   info=0;
//--- check
   if(npoints<=nvars+1 || nvars<1)
     {
      info=-1;
      return;
     }
//--- create variables
   int    i=0;
   double sigma2=0;
   int    i_=0;
//--- create array
   CRowDouble s;
//--- allocation
   s=vector<double>::Ones(npoints);
//--- function call
   LRBuildS(xy,s,npoints,nvars,info,lm,ar);
//--- check
   if(info<0)
      return;
//--- calculation
   sigma2=CMath::Sqr(ar.m_RMSError)*npoints/(npoints-nvars-1);
   ar.m_c*=sigma2;
  }
//+------------------------------------------------------------------+
//| Linear regression                                                |
//| Variant of LRBuild which uses vector of standatd deviations      |
//| (errors in function values).                                     |
//| INPUT PARAMETERS:                                                |
//|     XY          -   training set, array [0..NPoints-1,0..NVars]: |
//|                     * NVars columns - independent variables      |
//|                     * last column - dependent variable           |
//|     S           -   standard deviations (errors in function      |
//|                     values) array[0..NPoints-1], S[i]>0.         |
//|     NPoints     -   training set size, NPoints>NVars+1           |
//|     NVars       -   number of independent variables              |
//| OUTPUT PARAMETERS:                                               |
//|     Info        -   return code:                                 |
//|                     * -255, in case of unknown internal error    |
//|                     * -4, if internal SVD subroutine haven't     |
//|                     converged                                    |
//|                     * -1, if incorrect parameters was passed     |
//|                     (NPoints<NVars+2, NVars<1).                  |
//|                     * -2, if S[I]<=0                             |
//|                     *  1, if subroutine successfully finished    |
//|     LM          -   linear model in the ALGLIB format. Use       |
//|                     subroutines of this unit to work with the    |
//|                     model.                                       |
//|     AR          -   additional results                           |
//+------------------------------------------------------------------+
void CLinReg::LRBuildS(CMatrixDouble &xy,double &s[],const int npoints,
                       const int nvars,int &info,CLinearModel &lm,
                       CLRReport &ar)
  {
   CRowDouble S=s;
   LRBuildS(xy,S,npoints,nvars,info,lm,ar);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CLinReg::LRBuildS(CMatrixDouble &xy,CRowDouble &s,const int npoints,
                       const int nvars,int &info,CLinearModel &lm,
                       CLRReport &ar)
  {
//--- Test parameters
   if(npoints<=nvars+1 || nvars<1)
     {
      info=-1;
      return;
     }
//--- create variables
   double v=0;
   int    offs=0;
   double mean=0;
   double variance=0;
   double skewness=0;
   double kurtosis=0;
   int    i_=0;
//--- initialization
   info=0;
//--- creating arrays
   vector<double> means;
   vector<double> sigmas=vector<double>::Zeros(nvars);
   CRowDouble x;
//--- create array
   CMatrixDouble xyi;
//--- Copy data,add one more column (constant term)
   xyi=xy;
   xyi.Resize(npoints,nvars+2);
   xyi.Col(nvars+1,vector<double>::Ones(npoints));
   xyi.SwapCols(nvars,nvars+1);
//--- Standartization
   means=xyi.Mean(0);
   means.Resize(nvars);
   for(int i=0; i<nvars; i++)
     {
      x=xyi.Col(i)+0;
      //--- function call
      sigmas[i]=MathSqrt(CBaseStat::SampleVariance(x,npoints));
      if(sigmas[i]==0.0)
         sigmas[i]=1.0;
      xyi.Col(i,(xyi.Col(i)-means[i])/sigmas[i]);
     }
//--- Internal processing
   LRInternal(xyi,s,npoints,nvars+1,info,lm,ar);
//--- check
   if(info<0)
      return;
//--- Un-standartization
   offs=(int)MathRound(lm.m_w[3]);
   for(int j=0; j<nvars; j++)
     {
      //--- Constant term is updated (and its covariance too,
      //--- since it gets some variance from J-th component)
      v=means[j]/sigmas[j];
      lm.m_w.Add(offs+nvars,-lm.m_w[offs+j]*v);
      ar.m_c.Row(nvars,ar.m_c.Row(nvars)-ar.m_c.Row(j)*v);
      ar.m_c.Col(nvars,ar.m_c.Col(nvars)-ar.m_c.Col(j)*v);
      //--- J-th term is updated
      v=1/sigmas[j];
      lm.m_w.Mul(offs+j,v);
      ar.m_c.Row(j,ar.m_c[j]*v);
      ar.m_c.Col(j,ar.m_c.Col(j)*v);
     }
  }
//+------------------------------------------------------------------+
//| Like LRBuildS, but builds model                                  |
//|     Y=A(0)*X[0] + ... + A(N-1)*X[N-1]                            |
//| i.m_e. with zero constant term.                                  |
//+------------------------------------------------------------------+
void CLinReg::LRBuildZS(CMatrixDouble &xy,double &s[],const int npoints,
                        const int nvars,int &info,CLinearModel &lm,
                        CLRReport &ar)
  {
   CRowDouble S=s;
   LRBuildZS(xy,S,npoints,nvars,info,lm,ar);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CLinReg::LRBuildZS(CMatrixDouble &xy,CRowDouble &s,const int npoints,
                        const int nvars,int &info,CLinearModel &lm,
                        CLRReport &ar)
  {
//--- Test parameters
   if(npoints<=nvars+1 || nvars<1)
     {
      info=-1;
      return;
     }
//--- create variables
   double v=0;
   int    offs=0;
   double mean=0;
   double variance=0;
   double skewness=0;
   double kurtosis=0;
//--- initialization
   info=0;
//--- creating arrays
   CRowDouble x;
   CRowDouble c;
//--- create matrix
   CMatrixDouble xyi;
//--- Copy data,add one more column (constant term)
   xyi=xy;
   xyi.Resize(npoints,nvars+2);
   xyi.Col(nvars+1,vector<double>::Zeros(npoints));
   xyi.SwapCols(nvars,nvars+1);
//--- Standartization: unusual scaling
   c.Resize(nvars);
   for(int j=0; j<nvars; j++)
     {
      x=xy.Col(j)+0;
      //--- function call
      CBaseStat::SampleMoments(x,npoints,mean,variance,skewness,kurtosis);
      //--- check
      if(MathAbs(mean)>MathSqrt(variance))
        {
         //--- variation is relatively small,it is better to
         //--- bring mean value to 1
         c.Set(j,mean);
        }
      else
        {
         //--- variation is large,it is better to bring variance to 1
         if(variance==0.0)
            c.Set(j,1.0);
         else
            c.Set(j,MathSqrt(variance));
        }
      xyi.Col(j,x/c[j]+0);
     }
//--- Internal processing
   LRInternal(xyi,s,npoints,nvars+1,info,lm,ar);
//--- check
   if(info<0)
      return;
//--- Un-standartization
   offs=(int)MathRound(lm.m_w[3]);
   for(int j=0; j<nvars; j++)
     {
      //--- J-th term is updated
      v=1/c[j];
      lm.m_w.Mul(offs+j,v);
      ar.m_c.Row(j,ar.m_c[j]*v);
      ar.m_c.Col(j,ar.m_c.Col(j)*v);
     }
  }
//+------------------------------------------------------------------+
//| Like LRBuild but builds model                                    |
//|     Y=A(0)*X[0] + ... + A(N-1)*X[N-1]                            |
//| i.m_e. with zero constant term.                                  |
//+------------------------------------------------------------------+
void CLinReg::LRBuildZ(CMatrixDouble &xy,const int npoints,const int nvars,
                       int &info,CLinearModel &lm,CLRReport &ar)
  {
//--- check
   if(npoints<=nvars+1 || nvars<1)
     {
      info=-1;
      return;
     }
//--- create array
   CRowDouble s=vector<double>::Ones(npoints);
//--- initialization
   info=0;
//--- function call
   LRBuildZS(xy,s,npoints,nvars,info,lm,ar);
//--- check
   if(info<0)
      return;
//--- calculation
   double sigma2=CMath::Sqr(ar.m_RMSError)*npoints/(npoints-nvars-1);
   ar.m_c*=sigma2;
  }
//+------------------------------------------------------------------+
//| Unpacks coefficients of linear model.                            |
//| INPUT PARAMETERS:                                                |
//|     LM          -   linear model in ALGLIB format                |
//| OUTPUT PARAMETERS:                                               |
//|     V           -   coefficients,array[0..NVars]                 |
//|                     constant term (intercept) is stored in the   |
//|                     V[NVars].                                    |
//|     NVars       -   number of independent variables (one less    |
//|                     than number of coefficients)                 |
//+------------------------------------------------------------------+
void CLinReg::LRUnpack(CLinearModel &lm,double &v[],int &nvars)
  {
   CRowDouble V;
   LRUnpack(lm,V,nvars);
   V.ToArray(v);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CLinReg::LRUnpack(CLinearModel &lm,CRowDouble &v,int &nvars)
  {
//--- create variables
   int offs=0;
   int i1_=0;
//--- initialization
   nvars=0;
//--- check
   if(!CAp::Assert((int)MathRound(lm.m_w[1])==m_lrvnum,__FUNCTION__+": Incorrect LINREG version!"))
     {
      v.Resize(0);
      return;
     }
//--- change values
   nvars=(int)MathRound(lm.m_w[2]);
   offs=(int)MathRound(lm.m_w[3]);
//--- allocation
   v.Resize(nvars+1);
//--- calculation
   i1_=offs;
   for(int i_=0; i_<=nvars; i_++)
      v.Set(i_,lm.m_w[i_+i1_]);
  }
//+------------------------------------------------------------------+
//| "Packs" coefficients and creates linear model in ALGLIB format   |
//| (LRUnpack reversed).                                             |
//| INPUT PARAMETERS:                                                |
//|     V           -   coefficients, array[0..NVars]                |
//|     NVars       -   number of independent variables              |
//| OUTPUT PAREMETERS:                                               |
//|     LM          -   linear model.                                |
//+------------------------------------------------------------------+
void CLinReg::LRPack(double &v[],const int nvars,CLinearModel &lm)
  {
   CRowDouble V=v;
   LRPack(V,nvars,lm);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CLinReg::LRPack(CRowDouble &v,const int nvars,CLinearModel &lm)
  {
//--- create variables
   int offs=0;
   int i1_=0;
//--- allocation
   lm.m_w.Resize(5+nvars);
//--- change values
   offs=4;
   lm.m_w.Set(0,4+nvars+1);
   lm.m_w.Set(1,m_lrvnum);
   lm.m_w.Set(2,nvars);
   lm.m_w.Set(3,offs);
//--- calculation
   i1_=-offs;
   for(int i_=offs; i_<=offs+nvars; i_++)
      lm.m_w.Set(i_,v[i_+i1_]);
  }
//+------------------------------------------------------------------+
//| Procesing                                                        |
//| INPUT PARAMETERS:                                                |
//|     LM      -   linear model                                     |
//|     X       -   input vector, array[0..NVars-1].                 |
//| Result:                                                          |
//|     value of linear model regression estimate                    |
//+------------------------------------------------------------------+
double CLinReg::LRProcess(CLinearModel &lm,double &x[])
  {
   CRowDouble X=x;
   return(LRProcess(lm,X));
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double CLinReg::LRProcess(CLinearModel &lm,CRowDouble &x)
  {
//--- create variables
   double v=0;
   int    offs=0;
   int    nvars=0;
   int    i1_=0;
//--- check
   if(!CAp::Assert((int)MathRound(lm.m_w[1])==m_lrvnum,__FUNCTION__+": Incorrect LINREG version!"))
      return(EMPTY_VALUE);
//--- change values
   nvars=(int)MathRound(lm.m_w[2]);
   offs=(int)MathRound(lm.m_w[3]);
   i1_=offs;
   v=0.0;
//--- calculation
   for(int i_=0; i_<nvars; i_++)
      v+=x[i_]*lm.m_w[i_+i1_];
//--- return result
   return(v+lm.m_w[offs+nvars]);
  }
//+------------------------------------------------------------------+
//| RMS error on the test set                                        |
//| INPUT PARAMETERS:                                                |
//|     LM      -   linear model                                     |
//|     XY      -   test set                                         |
//|     NPoints -   test set size                                    |
//| RESULT:                                                          |
//|     root mean square error.                                      |
//+------------------------------------------------------------------+
double CLinReg::LRRMSError(CLinearModel &lm,CMatrixDouble &xy,
                           const int npoints)
  {
//--- check
   if(!CAp::Assert((int)MathRound(lm.m_w[1])==m_lrvnum,__FUNCTION__+": Incorrect LINREG version!"))
      return(EMPTY_VALUE);
//--- create variables
   double result=0;
   double v=0;
   int    nvars=(int)MathRound(lm.m_w[2]);
   CRowDouble x;
//--- calculation
   for(int i=0; i<npoints; i++)
     {
      x=xy[i]+0;
      v=LRProcess(lm,x);
      result+=CMath::Sqr(v-xy.Get(i,nvars));
     }
//--- return result
   return(MathSqrt(result/npoints));
  }
//+------------------------------------------------------------------+
//| Average error on the test set                                    |
//| INPUT PARAMETERS:                                                |
//|     LM      -   linear model                                     |
//|     XY      -   test set                                         |
//|     NPoints -   test set size                                    |
//| RESULT:                                                          |
//|     average error.                                               |
//+------------------------------------------------------------------+
double CLinReg::LRAvgError(CLinearModel &lm,CMatrixDouble &xy,
                           const int npoints)
  {
//--- check
   if(!CAp::Assert((int)MathRound(lm.m_w[1])==m_lrvnum,__FUNCTION__+": Incorrect LINREG version!"))
      return(EMPTY_VALUE);
//--- create variables
   double result=0;
   double v=0;
   int    nvars=(int)MathRound(lm.m_w[2]);
   CRowDouble x;
//--- calculation
   for(int i=0; i<npoints; i++)
     {
      x=xy[i]+0;
      v=LRProcess(lm,x);
      result+=MathAbs(v-xy.Get(i,nvars));
     }
//--- return result
   return(result/npoints);
  }
//+------------------------------------------------------------------+
//| RMS error on the test set                                        |
//| INPUT PARAMETERS:                                                |
//|     LM      -   linear model                                     |
//|     XY      -   test set                                         |
//|     NPoints -   test set size                                    |
//| RESULT:                                                          |
//|     average relative error.                                      |
//+------------------------------------------------------------------+
double CLinReg::LRAvgRelError(CLinearModel &lm,CMatrixDouble &xy,
                              const int npoints)
  {
//--- check
   if(!CAp::Assert((int)MathRound(lm.m_w[1])==m_lrvnum,__FUNCTION__+": Incorrect LINREG version!"))
      return(EMPTY_VALUE);
//--- create variables
   double result=0;
   int    k=0;
   double v=0;
   double y=0;
   int    nvars=(int)MathRound(lm.m_w[2]);
   CRowDouble x;
//--- calculation
   for(int i=0; i<npoints; i++)
     {
      y=xy.Get(i,nvars);
      //--- check
      if(y==0.0)
         continue;
      //--- calculation
      x=xy[i]+0;
      v=LRProcess(lm,x);
      //--- get result
      result+=MathAbs((v-y)/y);
      k++;
     }
//--- check
   if(k!=0)
      result/=k;
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| Copying of LinearModel strucure                                  |
//| INPUT PARAMETERS:                                                |
//|     LM1 -   original                                             |
//| OUTPUT PARAMETERS:                                               |
//|     LM2 -   copy                                                 |
//+------------------------------------------------------------------+
void CLinReg::LRCopy(CLinearModel &lm1,CLinearModel &lm2)
  {
   lm2.m_w=lm1.m_w;
  }
//+------------------------------------------------------------------+
//| Class method                                                     |
//+------------------------------------------------------------------+
void CLinReg::LRLines(CMatrixDouble &xy,double &s[],const int n,
                      int &info,double &a,double &b,double &vara,
                      double &varb,double &covab,double &corrab,
                      double &p)
  {
   CRowDouble S=s;
   LRLines(xy,S,n,info,a,b,vara,varb,covab,corrab,p);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CLinReg::LRLines(CMatrixDouble &xy,CRowDouble &s,const int n,
                      int &info,double &a,double &b,double &vara,
                      double &varb,double &covab,double &corrab,
                      double &p)
  {
//--- initialization
   info=0;
   a=0;
   b=0;
   vara=0;
   varb=0;
   covab=0;
   corrab=0;
   p=0;
//--- check
   if(n<2)
     {
      info=-1;
      return;
     }
   if(s.Min()<=0.0)
     {
      info=-2;
      return;
     }
//--- create variables
   double ss=0;
   double sx=0;
   double sxx=0;
   double sy=0;
   double stt=0;
   double e1=0;
   double e2=0;
   double t=0;
   double chi2=0;
//--- change value
   info=1;
//--- Calculate S,SX,SY,SXX
   vector<double> X=xy.Col(0);
   vector<double> Y=xy.Col(1);
   vector<double> S=s.ToVector();
   S.Resize(n);
   X.Resize(n);
   Y.Resize(n);
   vector<double> SS=MathPow(S*S,-1);
   ss=SS.Sum();
   sx=(X*SS).Sum();
   sy=(Y*SS).Sum();
   sxx=(X*X*SS).Sum();
//--- Test for condition number
   t=MathSqrt(4*CMath::Sqr(sx)+CMath::Sqr(ss-sxx));
   e1=0.5*(ss+sxx+t);
   e2=0.5*(ss+sxx-t);
//--- check
   if(MathMin(e1,e2)<=1000*CMath::m_machineepsilon*MathMax(e1,e2))
     {
      info=-3;
      return;
     }
//--- Calculate A,B
   vector<double> T=(X-sx/ss)/S;
   b=((T*Y)/S).Sum();
   stt=(T*T).Sum();
   b=b/stt;
   a=(sy-sx*b)/ss;
//--- Calculate goodness-of-fit
   if(n>2)
     {
      chi2=(MathPow((Y-X*b-a)/S,2)).Sum();
      //--- function call
      p=CIncGammaF::IncompleteGammaC((double)(n-2)/2.0,chi2/2.0);
     }
   else
      p=1;
//--- Calculate other parameters
   vara=(1+CMath::Sqr(sx)/(ss*stt))/ss;
   varb=1/stt;
   covab=-(sx/(ss*stt));
   corrab=covab/MathSqrt(vara*varb);
  }
//+------------------------------------------------------------------+
//| Class method                                                     |
//+------------------------------------------------------------------+
void CLinReg::LRLine(CMatrixDouble &xy,const int n,int &info,
                     double &a,double &b)
  {
//--- initialization
   info=0;
   a=0;
   b=0;
//--- check
   if(n<2)
     {
      info=-1;
      return;
     }
//--- create variables
   double vara=0;
   double varb=0;
   double covab=0;
   double corrab=0;
   double p=0;
//--- create array
   CRowDouble s=vector<double>::Ones(n);
//--- function call
   LRLines(xy,s,n,info,a,b,vara,varb,covab,corrab,p);
  }
//+------------------------------------------------------------------+
//| Internal linear regression subroutine                            |
//+------------------------------------------------------------------+
void CLinReg::LRInternal(CMatrixDouble &xy,CRowDouble &s,const int npoints,
                         const int nvars,int &info,CLinearModel &lm,
                         CLRReport &ar)
  {
//--- Check for errors in data
   if(npoints<nvars || nvars<1)
     {
      info=-1;
      return;
     }
   if(s.Min()<=0.0)
     {
      info=-2;
      return;
     }
//--- create variables
   CMatrixDouble a;
   CMatrixDouble u;
   CMatrixDouble vt;
   CMatrixDouble vm;
   CMatrixDouble xym;
   CRowDouble b;
   CRowDouble sv;
   CRowDouble t;
   CRowDouble svi;
   CRowDouble work;
   int i=0;
   int j=0;
   int k=0;
   int ncv=0;
   int na=0;
   int nacv=0;
   double r=0;
   double p=0;
   double epstol=1000;
   CLRReport ar2;
   int offs=0;
   CLinearModel tlm;
   int i_=0;
   int i1_=0;
//--- Check for errors in data
   info=1;
//--- Create design matrix
   a.Resize(npoints,nvars);
   b.Resize(npoints);
   for(i=0; i<npoints; i++)
     {
      r=1/s[i];
      for(i_=0; i_<=nvars-1; i_++)
         a.Set(i,i_,r*xy.Get(i,i_));
      b.Set(i,xy.Get(i,nvars)/s[i]);
     }
//--- Allocate W:
//--- W[0]     array size
//--- W[1]     version number, 0
//--- W[2]     NVars (minus 1, to be compatible with external representation)
//--- W[3]     coefficients offset
   lm.m_w.Resize(4+nvars);
   offs=4;
   lm.m_w.Set(0,4+nvars);
   lm.m_w.Set(1,m_lrvnum);
   lm.m_w.Set(2,nvars-1);
   lm.m_w.Set(3,offs);
//--- Solve problem using SVD:
//--- 0. check for degeneracy (different types)
//--- 1. A = U*diag(sv)*V'
//--- 2. T = b'*U
//--- 3. w = SUM((T[i]/sv[i])*V[..,i])
//--- 4. cov(wi,wj) = SUM(Vji*Vjk/sv[i]^2,K=1..M)
//--- see $15.4 of "Numerical Recipes in C" for more information
   t.Resize(nvars);
   svi.Resize(nvars);
   ar.m_c.Resize(nvars,nvars);
   vm.Resize(nvars,nvars);
   if(!CSingValueDecompose::RMatrixSVD(a,npoints,nvars,1,1,2,sv,u,vt))
     {
      info=-4;
      return;
     }
   if(sv[0]<=0.0)
     {
      //--- Degenerate case: zero design matrix.
      for(i=offs; i<offs+nvars; i++)
         lm.m_w.Set(i,0);
      ar.m_RMSError=LRRMSError(lm,xy,npoints);
      ar.m_AvgError=LRAvgError(lm,xy,npoints);
      ar.m_AvgRelError=LRAvgRelError(lm,xy,npoints);
      ar.m_cvrmserror=ar.m_RMSError;
      ar.m_cvavgerror=ar.m_AvgError;
      ar.m_cvavgrelerror=ar.m_AvgRelError;
      ar.m_ncvdefects=0;
      ar.m_cvdefects.Resize(nvars);
      ar.m_cvdefects.Fill(-1);
      ar.m_c=matrix<double>::Zeros(nvars,nvars);
      return;
     }
   if(sv[nvars-1]<=(epstol*CMath::m_machineepsilon*sv[0]))
     {
      //--- Degenerate case, non-zero design matrix.
      //--- We can leave it and solve task in SVD least squares fashion.
      //--- Solution and covariance matrix will be obtained correctly,
      //--- but CV error estimates - will not. It is better to reduce
      //--- it to non-degenerate task and to obtain correct CV estimates.
      for(k=nvars; k>=1; k--)
        {
         if(sv[k-1]>(epstol*CMath::m_machineepsilon*sv[0]))
           {
            //--- Reduce
            xym.Resize(npoints,k+1);
            for(i=0; i<npoints; i++)
              {
               for(j=0; j<k; j++)
                 {
                  r=CAblasF::RDotRR(nvars,xy,i,vt,j);
                  xym.Set(i,j,r);
                 }
               xym.Set(i,k,xy.Get(i,nvars));
              }
            //--- Solve
            LRInternal(xym,s,npoints,k,info,tlm,ar2);
            if(info!=1)
               return;
            //--- Convert back to un-reduced format
            for(j=0; j<nvars; j++)
               lm.m_w.Set(offs+j,0);
            for(j=0; j<k; j++)
              {
               r=tlm.m_w[offs+j];
               i1_=-offs;
               for(i_=offs; i_<offs+nvars; i_++)
                  lm.m_w.Add(i_,r*vt.Get(j,i_+i1_));
              }
            ar.m_RMSError=ar2.m_RMSError;
            ar.m_AvgError=ar2.m_AvgError;
            ar.m_AvgRelError=ar2.m_AvgRelError;
            ar.m_cvrmserror=ar2.m_cvrmserror;
            ar.m_cvavgerror=ar2.m_cvavgerror;
            ar.m_cvavgrelerror=ar2.m_cvavgrelerror;
            ar.m_ncvdefects=ar2.m_ncvdefects;
            ar.m_cvdefects.Resize(nvars);
            for(j=0; j<ar.m_ncvdefects; j++)
               ar.m_cvdefects.Set(j,ar2.m_cvdefects[j]);
            for(j=ar.m_ncvdefects; j<nvars; j++)
               ar.m_cvdefects.Set(j,-1);
            ar.m_c.Resize(nvars,nvars);
            work.Resize(nvars+1);
            CBlas::MatrixMatrixMultiply(ar2.m_c,0,k-1,0,k-1,false,vt,0,k-1,0,nvars-1,false,1.0,vm,0,k-1,0,nvars-1,0.0,work);
            CBlas::MatrixMatrixMultiply(vt,0,k-1,0,nvars-1,true,vm,0,k-1,0,nvars-1,false,1.0,ar.m_c,0,nvars-1,0,nvars-1,0.0,work);
            return;
           }
        }
      info=-255;
      return;
     }
   for(i=0; i<nvars; i++)
     {
      if(sv[i]>(epstol*CMath::m_machineepsilon*sv[0]))
         svi.Set(i,1/sv[i]);
      else
         svi.Set(i,0);
     }
   t=vector<double>::Zeros(nvars);
   for(i=0; i<npoints; i++)
     {
      r=b[i];
      for(i_=0; i_<nvars; i_++)
         t.Add(i_,r*u.Get(i,i_));
     }
   for(i=0; i<nvars; i++)
      lm.m_w.Set(offs+i,0);
   for(i=0; i<=nvars-1; i++)
     {
      r=t[i]*svi[i];
      i1_=-offs;
      for(i_=offs; i_<offs+nvars; i_++)
         lm.m_w.Add(i_,r*vt.Get(i,i_+i1_));
     }
   for(j=0; j<=nvars-1; j++)
     {
      r=svi[j];
      vm.Col(j,vt[j]*r);
     }
   for(i=0; i<nvars; i++)
     {
      for(j=i; j<nvars; j++)
        {
         r=CAblasF::RDotRR(nvars,vm,i,vm,j);
         ar.m_c.Set(i,j,r);
         ar.m_c.Set(j,i,r);
        }
     }
//--- Leave-1-out cross-validation error.
//--- NOTATIONS:
//--- A            design matrix
//--- A*x = b      original linear least squares task
//--- U*S*V'       SVD of A
//--- ai           i-th row of the A
//--- bi           i-th element of the b
//--- xf           solution of the original LLS task
//--- Cross-validation error of i-th element from a sample is
//--- calculated using following formula:
//---     ERRi = ai*xf - (ai*xf-bi*(ui*ui'))/(1-ui*ui')     (1)
//--- This formula can be derived from normal equations of the
//--- original task
//---     (A'*A)x = A'*b                                    (2)
//--- by applying modification (zeroing out i-th row of A) to (2):
//---     (A-ai)'*(A-ai) = (A-ai)'*b
//--- and using Sherman-Morrison formula for updating matrix inverse
//--- NOTE 1: b is not zeroed out since it is much simpler and
//--- does not influence final result.
//--- NOTE 2: some design matrices A have such ui that 1-ui*ui'=0.
//--- Formula (1) can't be applied for such cases and they are skipped
//--- from CV calculation (which distorts resulting CV estimate).
//--- But from the properties of U we can conclude that there can
//--- be no more than NVars such vectors. Usually
//--- NVars << NPoints, so in a normal case it only slightly
//--- influences result.
   ncv=0;
   na=0;
   nacv=0;
   ar.m_RMSError=0;
   ar.m_AvgError=0;
   ar.m_AvgRelError=0;
   ar.m_cvrmserror=0;
   ar.m_cvavgerror=0;
   ar.m_cvavgrelerror=0;
   ar.m_ncvdefects=0;
   ar.m_cvdefects.Resize(nvars);
   ar.m_cvdefects.Fill(-1,0,nvars);
   for(i=0; i<npoints; i++)
     {
      //--- Error on a training set
      i1_=offs;
      r=0.0;
      for(i_=0; i_<nvars; i_++)
         r+=xy.Get(i,i_)*lm.m_w[i_+i1_];
      ar.m_RMSError+=CMath::Sqr(r-xy.Get(i,nvars));
      ar.m_AvgError+=MathAbs(r-xy.Get(i,nvars));
      if(xy.Get(i,nvars)!=0.0)
        {
         ar.m_AvgRelError+=MathAbs((r-xy.Get(i,nvars))/xy.Get(i,nvars));
         na++;
        }
      //--- Error using fast leave-one-out cross-validation
      p=CAblasF::RDotRR(nvars,u,i,u,i);
      if(p>(1-epstol*CMath::m_machineepsilon))
        {
         ar.m_cvdefects.Set(ar.m_ncvdefects,i);
         ar.m_ncvdefects++;
         continue;
        }
      r=s[i]*(r/s[i]-b[i]*p)/(1-p);
      ar.m_cvrmserror+=CMath::Sqr(r-xy.Get(i,nvars));
      ar.m_cvavgerror+=MathAbs(r-xy.Get(i,nvars));
      if(xy.Get(i,nvars)!=0.0)
        {
         ar.m_cvavgrelerror+=MathAbs((r-xy.Get(i,nvars))/xy.Get(i,nvars));
         nacv++;
        }
      ncv++;
     }
   if(ncv==0)
     {
      //--- Something strange: ALL ui are degenerate.
      //--- Unexpected...
      info=-255;
      return;
     }

   ar.m_RMSError=MathSqrt(ar.m_RMSError/npoints);
   ar.m_AvgError=ar.m_AvgError/npoints;
   if(na!=0)
      ar.m_AvgRelError=ar.m_AvgRelError/na;
   ar.m_cvrmserror=MathSqrt(ar.m_cvrmserror/ncv);
   ar.m_cvavgerror=ar.m_cvavgerror/ncv;
   if(nacv!=0)
      ar.m_cvavgrelerror=ar.m_cvavgrelerror/nacv;
  }
//+------------------------------------------------------------------+
//| Model's errors:                                                  |
//|   * RelCLSError     -  fraction of misclassified cases.          |
//|   * AvgCE           -  acerage cross-entropy                     |
//|   * RMSError        -  root-mean-square error                    |
//|   * AvgError        -  average error                             |
//|   * AvgRelError     -  average relative error                    |
//| NOTE 1: RelCLSError/AvgCE are zero on regression problems.       |
//| NOTE 2: on classification problems RMSError/AvgError/AvgRelError |
//|         contain errors in prediction of posterior probabilities  |
//+------------------------------------------------------------------+
struct CModelErrors
  {
   double            m_RelCLSError;
   double            m_AvgCE;
   double            m_RMSError;
   double            m_AvgError;
   double            m_AvgRelError;
   //---
                     CModelErrors(void) { ZeroMemory(this); }
                    ~CModelErrors(void) {}
   //---
   void              Copy(const CModelErrors &obj);
   //--- overloading
   void              operator=(const CModelErrors &obj)  { Copy(obj); }
  };
//+------------------------------------------------------------------+
//| Copy                                                             |
//+------------------------------------------------------------------+
void CModelErrors::Copy(const CModelErrors &obj)
  {
   m_RelCLSError=obj.m_RelCLSError;
   m_AvgCE=obj.m_AvgCE;
   m_RMSError=obj.m_RMSError;
   m_AvgError=obj.m_AvgError;
   m_AvgRelError=obj.m_AvgRelError;
  }
//+------------------------------------------------------------------+
//|This structure is used to store MLP error and gradient.           |
//+------------------------------------------------------------------+
struct CSMLPGrad
  {
   double            m_F;
   CRowDouble        m_G;
   //---
                     CSMLPGrad(void) { m_F=0; }
                    ~CSMLPGrad(void) {}
   //---
   void              Copy(const CSMLPGrad &obj) { m_F=obj.m_F; m_G=obj.m_G; }
   //--- overloading
   void              operator=(const CSMLPGrad &obj) { Copy(obj); }
  };
//+------------------------------------------------------------------+
//| Auxiliary class for CMLPBase                                     |
//+------------------------------------------------------------------+
class CMultilayerPerceptron
  {
public:
   //--- variables
   int               m_hlnetworktype;
   int               m_hlnormtype;
   CModelErrors      m_err;
   CSMLPGrad         m_grad;
   //--- arrays
   CRowInt           m_dummyidx;
   CRowInt           m_hllayersizes;
   CRowInt           m_hlconnections;
   CRowInt           m_hlneurons;
   CRowInt           m_integerbuf;
   CRowInt           m_structinfo;
   CRowDouble        m_weights;
   CRowDouble        m_columnmeans;
   CRowDouble        m_columnsigmas;
   CRowDouble        m_neurons;
   CRowDouble        m_dfdnet;
   CRowDouble        m_derror;
   CRowDouble        m_x;
   CRowDouble        m_y;
   CRowDouble        m_xyrow;
   CRowDouble        m_nwbuf;
   CRowDouble        m_rndbuf;
   //--- matrix
   CMatrixDouble     m_xy;
   CMatrixDouble     m_dummydxy;
   CSparseMatrix     m_dummysxy;
   //--- constructor, destructor
                     CMultilayerPerceptron(void) { m_hlnetworktype=0; m_hlnormtype=0; }
                    ~CMultilayerPerceptron(void) {}
   //--- copy
   void              Copy(const CMultilayerPerceptron &obj);
   //--- overloading
   void              operator=(const CMultilayerPerceptron &obj)  { Copy(obj); }

  };
//+------------------------------------------------------------------+
//| Copy                                                             |
//+------------------------------------------------------------------+
void CMultilayerPerceptron::Copy(const CMultilayerPerceptron &obj)
  {
//--- copy variables
   m_hlnetworktype=obj.m_hlnetworktype;
   m_hlnormtype=obj.m_hlnormtype;
   m_err=obj.m_err;
   m_grad=obj.m_grad;
//--- copy arrays
   m_dummyidx=obj.m_dummyidx;
   m_hllayersizes=obj.m_hllayersizes;
   m_hlconnections=obj.m_hlconnections;
   m_hlneurons=obj.m_hlneurons;
   m_integerbuf=obj.m_integerbuf;
   m_structinfo=obj.m_structinfo;
   m_weights=obj.m_weights;
   m_columnmeans=obj.m_columnmeans;
   m_columnsigmas=obj.m_columnsigmas;
   m_neurons=obj.m_neurons;
   m_dfdnet=obj.m_dfdnet;
   m_derror=obj.m_derror;
   m_x=obj.m_x;
   m_y=obj.m_y;
   m_xyrow=obj.m_xyrow;
   m_nwbuf=obj.m_nwbuf;
   m_rndbuf=obj.m_rndbuf;
//--- copy matrix
   m_xy=obj.m_xy;
   m_dummydxy=obj.m_dummydxy;
   m_dummysxy=obj.m_dummysxy;
  }
//+------------------------------------------------------------------+
//| This class is a shell for class CMultilayerPerceptron            |
//+------------------------------------------------------------------+
class CMultilayerPerceptronShell
  {
private:
   CMultilayerPerceptron m_innerobj;

public:
   //--- constructors, destructor
                     CMultilayerPerceptronShell(void) {}
                     CMultilayerPerceptronShell(CMultilayerPerceptron &obj) { m_innerobj.Copy(obj); }
                    ~CMultilayerPerceptronShell(void) {}
   //--- method
   CMultilayerPerceptron *GetInnerObj(void) { return(GetPointer(m_innerobj)); }
  };
//+------------------------------------------------------------------+
//| Multilayer perceptron class                                      |
//+------------------------------------------------------------------+
class CMLPBase
  {
public:
   //--- variables
   static const int  m_mlpvnum;
   static const int  m_mlpfirstversion;
   static const int  m_nfieldwidth;
   static const int  m_hlconm_nfieldwidth;
   static const int  m_hlm_nfieldwidth;
   static const int  m_gradbasecasecost;
   static const int  m_microbatchsize;
   //--- public methods
   static int        MLPGradSplitCost(void);
   static int        MLPGradSplitSize(void);
   static void       MLPCreate0(const int nin,const int nout,CMultilayerPerceptron &network);
   static void       MLPCreate1(const int nin,const int nhid,const int nout,CMultilayerPerceptron &network);
   static void       MLPCreate2(const int nin,const int nhid1,const int nhid2,const int nout,CMultilayerPerceptron &network);
   static void       MLPCreateB0(const int nin,const int nout,const double b,double d,CMultilayerPerceptron &network);
   static void       MLPCreateB1(const int nin,const int nhid,const int nout,const double b,double d,CMultilayerPerceptron &network);
   static void       MLPCreateB2(const int nin,const int nhid1,const int nhid2,const int nout,const double b,double d,CMultilayerPerceptron &network);
   static void       MLPCreateR0(const int nin,const int nout,const double a,const double b,CMultilayerPerceptron &network);
   static void       MLPCreateR1(const int nin,const int nhid,const int nout,const double a,const double b,CMultilayerPerceptron &network);
   static void       MLPCreateR2(const int nin,const int nhid1,const int nhid2,const int nout,const double a,const double b,CMultilayerPerceptron &network);
   static void       MLPCreateC0(const int nin,const int nout,CMultilayerPerceptron &network);
   static void       MLPCreateC1(const int nin,const int nhid,const int nout,CMultilayerPerceptron &network);
   static void       MLPCreateC2(const int nin,const int nhid1,const int nhid2,const int nout,CMultilayerPerceptron &network);
   static void       MLPCopy(const CMultilayerPerceptron &network1,CMultilayerPerceptron &network2);
   static bool       MLPSameArchitecture(CMultilayerPerceptron &network1,CMultilayerPerceptron &network2);
   static void       MLPCopyTunableParameters(CMultilayerPerceptron &network1,CMultilayerPerceptron &network2);
   static void       MLPExportTunableParameters(CMultilayerPerceptron &network,CRowDouble &p,int &pcount);
   static void       MLPImportTunableParameters(CMultilayerPerceptron &network,CRowDouble &p);
   static void       MLPSerializeOld(CMultilayerPerceptron &network,double &ra[],int &rlen);
   static void       MLPSerializeOld(CMultilayerPerceptron &network,CRowDouble &ra,int &rlen);
   static void       MLPUnserializeOld(double &ra[],CMultilayerPerceptron &network);
   static void       MLPUnserializeOld(CRowDouble &ra,CMultilayerPerceptron &network);
   static void       MLPRandomize(CMultilayerPerceptron &network);
   static void       MLPRandomizeFull(CMultilayerPerceptron &network);
   static void       MLPInitPreprocessor(CMultilayerPerceptron &network,CMatrixDouble &xy,const int ssize);
   static void       MLPInitPreprocessorSparse(CMultilayerPerceptron &network,CSparseMatrix &xy,int ssize);
   static void       MLPInitPreprocessorSubset(CMultilayerPerceptron &network,CMatrixDouble &xy,int setsize,CRowInt &idx,int subsetsize);
   static void       MLPInitPreprocessorSparseSubset(CMultilayerPerceptron &network,CSparseMatrix &xy,int setsize,CRowInt &idx,int subsetsize);
   static void       MLPProperties(CMultilayerPerceptron &network,int &nin,int &nout,int &wcount);
   static int        MLPNTotal(CMultilayerPerceptron &network);
   static int        MLPGetInputsCount(CMultilayerPerceptron &network);
   static int        MLPGetOutputsCount(CMultilayerPerceptron &network);
   static int        MLPGetWeightsCount(CMultilayerPerceptron &network);
   static bool       MLPIsSoftMax(CMultilayerPerceptron &network);
   static int        MLPGetLayersCount(CMultilayerPerceptron &network);
   static int        MLPGetLayerSize(CMultilayerPerceptron &network,const int k);
   static void       MLPGetInputScaling(CMultilayerPerceptron &network,const int i,double &mean,double &sigma);
   static void       MLPGetOutputScaling(CMultilayerPerceptron &network,const int i,double &mean,double &sigma);
   static void       MLPGetNeuronInfo(CMultilayerPerceptron &network,const int k,const int i,int &fkind,double &threshold);
   static double     MLPGetWeight(CMultilayerPerceptron &network,const int k0,const int i0,const int k1,const int i1);
   static void       MLPSetInputScaling(CMultilayerPerceptron &network,const int i,const double mean,double sigma);
   static void       MLPSetOutputScaling(CMultilayerPerceptron &network,const int i,const double mean,double sigma);
   static void       MLPSetNeuronInfo(CMultilayerPerceptron &network,const int k,const int i,const int fkind,const double threshold);
   static void       MLPSetWeight(CMultilayerPerceptron &network,const int k0,const int i0,const int k1,const int i1,const double w);
   static void       MLPActivationFunction(double net,const int k,double &f,double &df,double &d2f);
   static void       MLPProcess(CMultilayerPerceptron &network,double &x[],double &y[]);
   static void       MLPProcess(CMultilayerPerceptron &network,CRowDouble &x,CRowDouble &y);
   static void       MLPProcessI(CMultilayerPerceptron &network,double &x[],double &y[]);
   static void       MLPProcessI(CMultilayerPerceptron &network,CRowDouble &x,CRowDouble &y);
   static double     MLPError(CMultilayerPerceptron &network,CMatrixDouble &xy,const int npoints);
   static double     MLPErrorSparse(CMultilayerPerceptron &network,CSparseMatrix &xy,int npoints);
   static double     MLPErrorN(CMultilayerPerceptron &network,CMatrixDouble &xy,const int ssize);
   static int        MLPClsError(CMultilayerPerceptron &network,CMatrixDouble &xy,const int ssize);
   static double     MLPRelClsError(CMultilayerPerceptron &network,CMatrixDouble &xy,const int npoints);
   static double     MLPRelClsErrorSparse(CMultilayerPerceptron &network,CSparseMatrix &xy,int npoints);
   static double     MLPAvgCE(CMultilayerPerceptron &network,CMatrixDouble &xy,const int npoints);
   static double     MLPAvgCESparse(CMultilayerPerceptron &network,CSparseMatrix &xy,int npoints);
   static double     MLPRMSError(CMultilayerPerceptron &network,CMatrixDouble &xy,const int npoints);
   static double     MLPRMSErrorSparse(CMultilayerPerceptron &network,CSparseMatrix &xy,int npoints);
   static double     MLPAvgError(CMultilayerPerceptron &network,CMatrixDouble &xy,const int npoints);
   static double     MLPAvgErrorSparse(CMultilayerPerceptron &network,CSparseMatrix &xy,int npoints);
   static double     MLPAvgRelError(CMultilayerPerceptron &network,CMatrixDouble &xy,const int npoints);
   static double     MLPAvgRelErrorSparse(CMultilayerPerceptron &network,CSparseMatrix &xy,int npoints);
   static void       MLPGrad(CMultilayerPerceptron &network,double &x[],double &desiredy[],double &e,double &grad[]);
   static void       MLPGrad(CMultilayerPerceptron &network,CRowDouble &x,CRowDouble &desiredy,double &e,CRowDouble &grad);
   static void       MLPGradN(CMultilayerPerceptron &network,double &x[],double &desiredy[],double &e,double &grad[]);
   static void       MLPGradN(CMultilayerPerceptron &network,CRowDouble &x,CRowDouble &desiredy,double &e,CRowDouble &grad);
   static void       MLPGradBatch(CMultilayerPerceptron &network,CMatrixDouble &xy,const int ssize,double &e,double &grad[]);
   static void       MLPGradBatch(CMultilayerPerceptron &network,CMatrixDouble &xy,const int ssize,double &e,CRowDouble &grad);
   static void       MLPGradBatchSparse(CMultilayerPerceptron &network,CSparseMatrix &xy,int ssize,double &e,CRowDouble &grad);
   static void       MLPGradBatchSubset(CMultilayerPerceptron &network,CMatrixDouble &xy,int setsize,CRowInt &idx,int subsetsize,double &e,CRowDouble &grad);
   static void       MLPGradBatchSparseSubset(CMultilayerPerceptron &network,CSparseMatrix &xy,int setsize,CRowInt &idx,int subsetsize,double &e,CRowDouble &grad);
   static void       MLPGradBatchX(CMultilayerPerceptron &network,CMatrixDouble &densexy,CSparseMatrix &sparsexy,int datasetsize,int datasettype,CRowInt &idx,int subset0,int subset1,int subsettype,double &e,CRowDouble &grad);
   static void       MLPGradNBatch(CMultilayerPerceptron &network,CMatrixDouble &xy,const int ssize,double &e,double &grad[]);
   static void       MLPGradNBatch(CMultilayerPerceptron &network,CMatrixDouble &xy,const int ssize,double &e,CRowDouble &grad);
   static void       MLPHessianNBatch(CMultilayerPerceptron &network,CMatrixDouble &xy,const int ssize,double &e,double &grad[],CMatrixDouble &h);
   static void       MLPHessianNBatch(CMultilayerPerceptron &network,CMatrixDouble &xy,const int ssize,double &e,CRowDouble &grad,CMatrixDouble &h);
   static void       MLPHessianBatch(CMultilayerPerceptron &network,CMatrixDouble &xy,const int ssize,double &e,double &grad[],CMatrixDouble &h);
   static void       MLPHessianBatch(CMultilayerPerceptron &network,CMatrixDouble &xy,const int ssize,double &e,CRowDouble &grad,CMatrixDouble &h);
   static void       MLPInternalProcessVector(int &structinfo[],double &weights[],double &columnmeans[],double &columnsigmas[],double &neurons[],double &dfdnet[],double &x[],double &y[]);
   static void       MLPInternalProcessVector(CRowInt &structinfo,CRowDouble &weights,CRowDouble &columnmeans,CRowDouble &columnsigmas,CRowDouble &neurons,CRowDouble &dfdnet,CRowDouble &x,CRowDouble &y);
   static void       MLPAlloc(CSerializer &s,CMultilayerPerceptron &network);
   static void       MLPSerialize(CSerializer &s,CMultilayerPerceptron &network);
   static void       MLPUnserialize(CSerializer &s,CMultilayerPerceptron &network);
   static void       MLPAllErrorsSubset(CMultilayerPerceptron &network,CMatrixDouble &xy,int setsize,CRowInt &subset,int subsetsize,CModelErrors &rep);
   static void       MLPAllErrorsSparseSubset(CMultilayerPerceptron &network,CSparseMatrix &xy,int setsize,CRowInt &subset,int subsetsize,CModelErrors &rep);
   static double     MLPErrorSubset(CMultilayerPerceptron &network,CMatrixDouble &xy,int setsize,CRowInt &subset,int subsetsize);
   static double     MLPErrorSparseSubset(CMultilayerPerceptron &network,CSparseMatrix &xy,int setsize,CRowInt &subset,int subsetsize);
   static void       MLPAllErrorsX(CMultilayerPerceptron &network,CMatrixDouble &densexy,CSparseMatrix &sparsexy,int datasetsize,int datasettype,CRowInt &idx,int subset0,int subset1,int subsettype,CModelErrors &rep);

private:
   static void       AddInputLayer(const int ncount,CRowInt &lsizes,CRowInt &ltypes,CRowInt &lconnfirst,CRowInt &lconnlast,int &lastproc);
   static void       AddBiasedSummatorLayer(const int ncount,CRowInt &lsizes,CRowInt &ltypes,CRowInt &lconnfirst,CRowInt &lconnlast,int &lastproc);
   static void       AddActivationLayer(const int functype,CRowInt &lsizes,CRowInt &ltypes,CRowInt &lconnfirst,CRowInt &lconnlast,int &lastproc);
   static void       AddZeroLayer(CRowInt &lsizes,CRowInt &ltypes,CRowInt &lconnfirst,CRowInt &lconnlast,int &lastproc);
   static void       HLAddInputLayer(CMultilayerPerceptron &network,int &connidx,int &neuroidx,int &structinfoidx,int nin);
   static void       HLAddOutputLayer(CMultilayerPerceptron &network,int &connidx,int &neuroidx,int &structinfoidx,int &weightsidx,const int k,const int nprev,const int nout,const bool iscls,const bool islinearout);
   static void       HLAddHiddenLayer(CMultilayerPerceptron &network,int &connidx,int &neuroidx,int &structinfoidx,int &weightsidx,const int k,const int nprev,const int ncur);
   static void       FillHighLevelInformation(CMultilayerPerceptron &network,const int nin,const int nhid1,const int nhid2,const int nout,const bool iscls,const bool islinearout);
   static void       MLPCreate(const int nin,const int nout,CRowInt &lsizes,CRowInt &ltypes,CRowInt &lconnfirst,CRowInt &lconnlast,const int layerscount,const bool isclsnet,CMultilayerPerceptron &network);
   static void       MLPHessianBatchInternal(CMultilayerPerceptron &network,CMatrixDouble &xy,const int ssize,const bool naturalerr,double &e,CRowDouble &grad,CMatrixDouble &h);
   static void       MLPInternalCalculateGradient(CMultilayerPerceptron &network,CRowDouble &neurons,CRowDouble &weights,CRowDouble &derror,CRowDouble &grad,const bool naturalerrorfunc);
   static void       MLPChunkedGradient(CMultilayerPerceptron &network,CMatrixDouble &xy,const int cstart,const int csize,CRowDouble &batch4buf,CRowDouble &hpcbuf,double &e,const bool naturalerrorfunc);
   static void       MLPChunkedProcess(CMultilayerPerceptron &network,CMatrixDouble &xy,int cstart,int csize,CRowDouble &batch4buf,CRowDouble &hpcbuf);
   static double     SafeCrossEntropy(const double t,const double z);
   static void       RandomizeBackwardPass(CMultilayerPerceptron &network,int neuronidx,double v);
  };
//+------------------------------------------------------------------+
//| Initialize constants                                             |
//+------------------------------------------------------------------+
const int CMLPBase::m_mlpvnum=7;
const int CMLPBase::m_mlpfirstversion=0;
const int CMLPBase::m_nfieldwidth=4;
const int CMLPBase::m_hlconm_nfieldwidth=5;
const int CMLPBase::m_hlm_nfieldwidth=4;
const int CMLPBase::m_gradbasecasecost=50000;
const int CMLPBase::m_microbatchsize=64;
//+------------------------------------------------------------------+
//| This function returns number of weights updates which is required|
//| for gradient calculation problem to be splitted.                 |
//+------------------------------------------------------------------+
int CMLPBase::MLPGradSplitCost(void)
  {
   return(m_gradbasecasecost);
  }
//+------------------------------------------------------------------+
//| This function returns number of elements in subset of dataset    |
//| which is required for gradient calculation problem to be splitted|
//+------------------------------------------------------------------+
int CMLPBase::MLPGradSplitSize(void)
  {
   return(m_microbatchsize);
  }
//+------------------------------------------------------------------+
//| Creates  neural  network  with  NIn  inputs,  NOut outputs,      |
//| without hidden layers, with linear output layer. Network weights |
//| are filled with small random values.                             |
//+------------------------------------------------------------------+
void CMLPBase::MLPCreate0(const int nin,const int nout,
                          CMultilayerPerceptron &network)
  {
//--- create variables
   int layerscount=0;
   int lastproc=0;
//--- creating arrays
   CRowInt lsizes;
   CRowInt ltypes;
   CRowInt lconnfirst;
   CRowInt lconnlast;
//--- initialization
   layerscount=4;
//--- Allocate arrays
   lsizes.Resize(layerscount);
   ltypes.Resize(layerscount);
   lconnfirst.Resize(layerscount);
   lconnlast.Resize(layerscount);
//--- Layers
   AddInputLayer(nin,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddBiasedSummatorLayer(nout,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddActivationLayer(-5,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- Create
   MLPCreate(nin,nout,lsizes,ltypes,lconnfirst,lconnlast,layerscount,false,network);
//--- function call
   FillHighLevelInformation(network,nin,0,0,nout,false,true);
  }
//+------------------------------------------------------------------+
//| Same as MLPCreate0, but with one hidden layer (NHid neurons) with|
//| non-linear activation function. Output layer is linear.          |
//+------------------------------------------------------------------+
void CMLPBase::MLPCreate1(const int nin,const int nhid,const int nout,
                          CMultilayerPerceptron &network)
  {
//--- create variables
   int layerscount=0;
   int lastproc=0;
//--- creating arrays
   CRowInt lsizes;
   CRowInt ltypes;
   CRowInt lconnfirst;
   CRowInt lconnlast;
//--- create variables
   layerscount=7;
//--- Allocate arrays
   lsizes.Resize(layerscount);
   ltypes.Resize(layerscount);
   lconnfirst.Resize(layerscount);
   lconnlast.Resize(layerscount);
//--- Layers
   AddInputLayer(nin,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddBiasedSummatorLayer(nhid,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddActivationLayer(1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddBiasedSummatorLayer(nout,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddActivationLayer(-5,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- Create
   MLPCreate(nin,nout,lsizes,ltypes,lconnfirst,lconnlast,layerscount,false,network);
//--- function call
   FillHighLevelInformation(network,nin,nhid,0,nout,false,true);
  }
//+------------------------------------------------------------------+
//| Same as MLPCreate0,but with two hidden layers (NHid1 and NHid2   |
//| neurons) with non-linear activation function. Output layer is    |
//| linear.                                                          |
//|  $ALL                                                            |
//+------------------------------------------------------------------+
void CMLPBase::MLPCreate2(const int nin,const int nhid1,const int nhid2,
                          const int nout,CMultilayerPerceptron &network)
  {
//--- create variables
   int layerscount=0;
   int lastproc=0;
//--- creating arrays
   CRowInt lsizes;
   CRowInt ltypes;
   CRowInt lconnfirst;
   CRowInt lconnlast;
//--- initialization
   layerscount=10;
//--- Allocate arrays
   lsizes.Resize(layerscount);
   ltypes.Resize(layerscount);
   lconnfirst.Resize(layerscount);
   lconnlast.Resize(layerscount);
//--- Layers
   AddInputLayer(nin,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddBiasedSummatorLayer(nhid1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddActivationLayer(1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddBiasedSummatorLayer(nhid2,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddActivationLayer(1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddBiasedSummatorLayer(nout,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddActivationLayer(-5,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- Create
   MLPCreate(nin,nout,lsizes,ltypes,lconnfirst,lconnlast,layerscount,false,network);
//--- function call
   FillHighLevelInformation(network,nin,nhid1,nhid2,nout,false,true);
  }
//+------------------------------------------------------------------+
//| Creates neural network with NIn inputs, NOut outputs, without    |
//| hidden layers with non-linear output layer. Network weights are  |
//| filled with small random values.                                 |
//| Activation function of the output layer takes values:            |
//|     (B, +INF), if D>=0                                           |
//| or                                                               |
//|     (-INF, B), if D<0.                                           |
//+------------------------------------------------------------------+
void CMLPBase::MLPCreateB0(const int nin,const int nout,const double b,
                           double d,CMultilayerPerceptron &network)
  {
//--- create variables
   int layerscount=0;
   int lastproc=0;
//--- creating arrays
   CRowInt lsizes;
   CRowInt ltypes;
   CRowInt lconnfirst;
   CRowInt lconnlast;
//--- initialization
   layerscount=4;
//--- check
   if(d>=0.0)
      d=1;
   else
      d=-1;
//--- Allocate arrays
   lsizes.Resize(layerscount);
   ltypes.Resize(layerscount);
   lconnfirst.Resize(layerscount);
   lconnlast.Resize(layerscount);
//--- Layers
   AddInputLayer(nin,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddBiasedSummatorLayer(nout,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddActivationLayer(3,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- Create
   MLPCreate(nin,nout,lsizes,ltypes,lconnfirst,lconnlast,layerscount,false,network);
//--- function call
   FillHighLevelInformation(network,nin,0,0,nout,false,false);
//--- Turn on ouputs shift/scaling.
   for(int i=nin; i<nin+nout; i++)
     {
      network.m_columnmeans.Set(i,b);
      network.m_columnsigmas.Set(i,d);
     }
  }
//+------------------------------------------------------------------+
//| Same as MLPCreateB0 but with non-linear hidden layer.            |
//+------------------------------------------------------------------+
void CMLPBase::MLPCreateB1(const int nin,const int nhid,const int nout,
                           const double b,double d,CMultilayerPerceptron &network)
  {
//--- create variables
   int layerscount=0;
   int lastproc=0;
//--- creating arrays
   CRowInt lsizes;
   CRowInt ltypes;
   CRowInt lconnfirst;
   CRowInt lconnlast;
   layerscount=7;
//--- check
   if(d>=0.0)
      d=1;
   else
      d=-1;
//--- Allocate arrays
   lsizes.Resize(layerscount);
   ltypes.Resize(layerscount);
   lconnfirst.Resize(layerscount);
   lconnlast.Resize(layerscount);
//--- Layers
   AddInputLayer(nin,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddBiasedSummatorLayer(nhid,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddActivationLayer(1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddBiasedSummatorLayer(nout,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddActivationLayer(3,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- Create
   MLPCreate(nin,nout,lsizes,ltypes,lconnfirst,lconnlast,layerscount,false,network);
//--- function call
   FillHighLevelInformation(network,nin,nhid,0,nout,false,false);
//--- Turn on ouputs shift/scaling.
   for(int i=nin; i<nin+nout; i++)
     {
      network.m_columnmeans.Set(i,b);
      network.m_columnsigmas.Set(i,d);
     }
  }
//+------------------------------------------------------------------+
//| Same as MLPCreateB0 but with two non-linear hidden layers.       |
//+------------------------------------------------------------------+
void CMLPBase::MLPCreateB2(const int nin,const int nhid1,const int nhid2,
                           const int nout,const double b,double d,
                           CMultilayerPerceptron &network)
  {
//--- create variables
   int layerscount=0;
   int lastproc=0;
//--- creating arrays
   CRowInt lsizes;
   CRowInt ltypes;
   CRowInt lconnfirst;
   CRowInt lconnlast;
//--- initialization
   layerscount=10;
//--- check
   if(d>=0.0)
      d=1;
   else
      d=-1;
//--- Allocate arrays
   lsizes.Resize(layerscount);
   ltypes.Resize(layerscount);
   lconnfirst.Resize(layerscount);
   lconnlast.Resize(layerscount);
//--- Layers
   AddInputLayer(nin,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddBiasedSummatorLayer(nhid1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddActivationLayer(1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddBiasedSummatorLayer(nhid2,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddActivationLayer(1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddBiasedSummatorLayer(nout,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddActivationLayer(3,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- Create
   MLPCreate(nin,nout,lsizes,ltypes,lconnfirst,lconnlast,layerscount,false,network);
//--- function call
   FillHighLevelInformation(network,nin,nhid1,nhid2,nout,false,false);
//--- Turn on ouputs shift/scaling.
   for(int i=nin; i<nin+nout; i++)
     {
      network.m_columnmeans.Set(i,b);
      network.m_columnsigmas.Set(i,d);
     }
  }
//+------------------------------------------------------------------+
//| Creates  neural  network  with  NIn  inputs,  NOut outputs,      |
//| without hidden layers with non-linear output layer. Network      |
//| weights are filled with small random values. Activation function |
//| of the output layer takes values [A,B].                          |
//+------------------------------------------------------------------+
void CMLPBase::MLPCreateR0(const int nin,const int nout,const double a,
                           const double b,CMultilayerPerceptron &network)
  {
//--- create variables
   int layerscount=0;
   int lastproc=0;
//--- creating arrays
   CRowInt lsizes;
   CRowInt ltypes;
   CRowInt lconnfirst;
   CRowInt lconnlast;
//--- initialization
   layerscount=4;
//--- Allocate arrays
   lsizes.Resize(layerscount);
   ltypes.Resize(layerscount);
   lconnfirst.Resize(layerscount);
   lconnlast.Resize(layerscount);
//--- Layers
   AddInputLayer(nin,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddBiasedSummatorLayer(nout,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddActivationLayer(1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- Create
   MLPCreate(nin,nout,lsizes,ltypes,lconnfirst,lconnlast,layerscount,false,network);
//--- function call
   FillHighLevelInformation(network,nin,0,0,nout,false,false);
//--- Turn on outputs shift/scaling.
   for(int i=nin; i<nin+nout; i++)
     {
      network.m_columnmeans.Set(i,0.5*(a+b));
      network.m_columnsigmas.Set(i,0.5*(a-b));
     }
  }
//+------------------------------------------------------------------+
//| Same as MLPCreateR0,but with non-linear hidden layer.            |
//+------------------------------------------------------------------+
void CMLPBase::MLPCreateR1(const int nin,const int nhid,const int nout,
                           const double a,const double b,
                           CMultilayerPerceptron &network)
  {
//--- create variables
   int layerscount=0;
   int lastproc=0;
//--- creating arrays
   CRowInt lsizes;
   CRowInt ltypes;
   CRowInt lconnfirst;
   CRowInt lconnlast;
//--- initialization
   layerscount=7;
//--- Allocate arrays
   lsizes.Resize(layerscount);
   ltypes.Resize(layerscount);
   lconnfirst.Resize(layerscount);
   lconnlast.Resize(layerscount);
//--- Layers
   AddInputLayer(nin,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddBiasedSummatorLayer(nhid,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddActivationLayer(1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddBiasedSummatorLayer(nout,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddActivationLayer(1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- Create
   MLPCreate(nin,nout,lsizes,ltypes,lconnfirst,lconnlast,layerscount,false,network);
//--- function call
   FillHighLevelInformation(network,nin,nhid,0,nout,false,false);
//--- Turn on outputs shift/scaling.
   for(int i=nin; i<nin+nout; i++)
     {
      network.m_columnmeans.Set(i,0.5*(a+b));
      network.m_columnsigmas.Set(i,0.5*(a-b));
     }
  }
//+------------------------------------------------------------------+
//| Same as MLPCreateR0,but with two non-linear hidden layers.       |
//+------------------------------------------------------------------+
void CMLPBase::MLPCreateR2(const int nin,const int nhid1,const int nhid2,
                           const int nout,const double a,const double b,
                           CMultilayerPerceptron &network)
  {
//--- create variables
   int layerscount=0;
   int lastproc=0;
//--- creating arrays
   CRowInt lsizes;
   CRowInt ltypes;
   CRowInt lconnfirst;
   CRowInt lconnlast;
//--- initialization
   layerscount=10;
//--- Allocate arrays
   lsizes.Resize(layerscount);
   ltypes.Resize(layerscount);
   lconnfirst.Resize(layerscount);
   lconnlast.Resize(layerscount);
//--- Layers
   AddInputLayer(nin,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddBiasedSummatorLayer(nhid1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddActivationLayer(1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddBiasedSummatorLayer(nhid2,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddActivationLayer(1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddBiasedSummatorLayer(nout,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddActivationLayer(1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- Create
   MLPCreate(nin,nout,lsizes,ltypes,lconnfirst,lconnlast,layerscount,false,network);
//--- function call
   FillHighLevelInformation(network,nin,nhid1,nhid2,nout,false,false);
//--- Turn on outputs shift/scaling.
   for(int i=nin; i<nin+nout; i++)
     {
      network.m_columnmeans.Set(i,0.5*(a+b));
      network.m_columnsigmas.Set(i,0.5*(a-b));
     }
  }
//+------------------------------------------------------------------+
//| Creates classifier network with NIn inputs and NOut possible     |
//| classes.                                                         |
//| Network contains no hidden layers and linear output layer with   |
//| SOFTMAX-normalization (so outputs sums up to 1.0 and converge to |
//| posterior probabilities).                                        |
//+------------------------------------------------------------------+
void CMLPBase::MLPCreateC0(const int nin,const int nout,
                           CMultilayerPerceptron &network)
  {
//--- create variables
   int layerscount=0;
   int lastproc=0;
//--- creating arrays
   CRowInt lsizes;
   CRowInt ltypes;
   CRowInt lconnfirst;
   CRowInt lconnlast;
//--- check
   if(!CAp::Assert(nout>=2,__FUNCTION__+": NOut<2!"))
      return;
//--- initialization
   layerscount=4;
//--- Allocate arrays
   lsizes.Resize(layerscount);
   ltypes.Resize(layerscount);
   lconnfirst.Resize(layerscount);
   lconnlast.Resize(layerscount);
//--- Layers
   AddInputLayer(nin,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddBiasedSummatorLayer(nout-1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddZeroLayer(lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- Create
   MLPCreate(nin,nout,lsizes,ltypes,lconnfirst,lconnlast,layerscount,true,network);
//--- function call
   FillHighLevelInformation(network,nin,0,0,nout,true,true);
  }
//+------------------------------------------------------------------+
//| Same as MLPCreateC0,but with one non-linear hidden layer.        |
//+------------------------------------------------------------------+
void CMLPBase::MLPCreateC1(const int nin,const int nhid,const int nout,
                           CMultilayerPerceptron &network)
  {
//--- create variables
   int layerscount=0;
   int lastproc=0;
//--- creating arrays
   CRowInt lsizes;
   CRowInt ltypes;
   CRowInt lconnfirst;
   CRowInt lconnlast;
//--- check
   if(!CAp::Assert(nout>=2,__FUNCTION__+": NOut<2!"))
      return;
//--- initialization
   layerscount=7;
//--- Allocate arrays
   lsizes.Resize(layerscount);
   ltypes.Resize(layerscount);
   lconnfirst.Resize(layerscount);
   lconnlast.Resize(layerscount);
//--- Layers
   AddInputLayer(nin,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddBiasedSummatorLayer(nhid,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddActivationLayer(1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddBiasedSummatorLayer(nout-1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddZeroLayer(lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- Create
   MLPCreate(nin,nout,lsizes,ltypes,lconnfirst,lconnlast,layerscount,true,network);
//--- function call
   FillHighLevelInformation(network,nin,nhid,0,nout,true,true);
  }
//+------------------------------------------------------------------+
//| Same as MLPCreateC0, but with two non-linear hidden layers.      |
//+------------------------------------------------------------------+
void CMLPBase::MLPCreateC2(const int nin,const int nhid1,const int nhid2,
                           const int nout,CMultilayerPerceptron &network)
  {
//--- create variables
   int layerscount=0;
   int lastproc=0;
//--- creating arrays
   CRowInt lsizes;
   CRowInt ltypes;
   CRowInt lconnfirst;
   CRowInt lconnlast;
//--- check
   if(!CAp::Assert(nout>=2,__FUNCTION__+": NOut<2!"))
      return;
//--- initialization
   layerscount=10;
//--- Allocate arrays
   lsizes.Resize(layerscount);
   ltypes.Resize(layerscount);
   lconnfirst.Resize(layerscount);
   lconnlast.Resize(layerscount);
//--- Layers
   AddInputLayer(nin,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddBiasedSummatorLayer(nhid1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddActivationLayer(1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddBiasedSummatorLayer(nhid2,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddActivationLayer(1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddBiasedSummatorLayer(nout-1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
   AddZeroLayer(lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- Create
   MLPCreate(nin,nout,lsizes,ltypes,lconnfirst,lconnlast,layerscount,true,network);
//--- function call
   FillHighLevelInformation(network,nin,nhid1,nhid2,nout,true,true);
  }
//+------------------------------------------------------------------+
//| Copying of neural network                                        |
//| INPUT PARAMETERS:                                                |
//|     Network1 -   original                                        |
//| OUTPUT PARAMETERS:                                               |
//|     Network2 -   copy                                            |
//+------------------------------------------------------------------+
void CMLPBase::MLPCopy(const CMultilayerPerceptron &network1,
                       CMultilayerPerceptron &network2)
  {
//--- copy
   network2=network1;
   network2.m_grad.m_F=0;
   network2.m_grad.m_G.Fill(0);
  }
//+------------------------------------------------------------------+
//| This function compares architectures of neural networks. Only    |
//| geometries are compared, weights and other parameters are        |
//| not tested.                                                      |
//+------------------------------------------------------------------+
bool CMLPBase::MLPSameArchitecture(CMultilayerPerceptron &network1,
                                   CMultilayerPerceptron &network2)
  {
//--- check
   if(!CAp::Assert(network1.m_structinfo.Size()>0 && network1.m_structinfo.Size()>=network1.m_structinfo[0],__FUNCTION__": Network1 is uninitialized"))
      return(false);
   if(!CAp::Assert(network2.m_structinfo.Size()>0 && network2.m_structinfo.Size()>=network2.m_structinfo[0],__FUNCTION__": Network2 is uninitialized"))
      return(false);
   if(network1.m_structinfo[0]!=network2.m_structinfo[0])
      return(false);

   int ninfo=network1.m_structinfo[0];
   for(int i=0; i<ninfo; i++)
      if(network1.m_structinfo[i]!=network2.m_structinfo[i])
         return(false);

   return(true);
  }
//+------------------------------------------------------------------+
//| This function copies tunable parameters (weights/means/sigmas)   |
//| from one network to another with same architecture. It performs  |
//| some rudimentary checks that architectures are same, and throws  |
//| exception if check fails.                                        |
//| It is intended for fast copying of states between two network    |
//| which are known to have same geometry.                           |
//| INPUT PARAMETERS:                                                |
//|   Network1    -  source, must be correctly initialized           |
//|   Network2    -  target, must have same architecture             |
//| OUTPUT PARAMETERS:                                               |
//|   Network2    -  network state is copied from source to target   |
//+------------------------------------------------------------------+
void CMLPBase::MLPCopyTunableParameters(CMultilayerPerceptron &network1,
                                        CMultilayerPerceptron &network2)
  {
//--- create variables
   int nin=0;
   int nout=0;
   int wcount=0;
//--- check
   if(!CAp::Assert(network1.m_structinfo.Size()>0 && network1.m_structinfo.Size()>=network1.m_structinfo[0],__FUNCTION__": Network1 is uninitialized"))
      return;
   if(!CAp::Assert(network2.m_structinfo.Size()>0 && network2.m_structinfo.Size()>=network2.m_structinfo[0],__FUNCTION__": Network2 is uninitialized"))
      return;
   if(!CAp::Assert(network1.m_structinfo[0]==network2.m_structinfo[0],__FUNCTION__": Network1 geometry differs from that of Network2"))
      return;

   int ninfo=network1.m_structinfo[0];
   for(int i=0; i<ninfo; i++)
      if(!CAp::Assert(network1.m_structinfo[i]==network2.m_structinfo[i],__FUNCTION__": Network1 geometry differs from that of Network2"))
         return;

   MLPProperties(network1,nin,nout,wcount);
   network2.m_weights=network1.m_weights;
   if(MLPIsSoftMax(network1))
     {
      network2.m_columnmeans=network1.m_columnmeans;
      network2.m_columnsigmas=network1.m_columnsigmas;
     }
   else
     {
      network2.m_columnmeans=network1.m_columnmeans;
      network2.m_columnsigmas=network1.m_columnsigmas;
     }
  }
//+------------------------------------------------------------------+
//| This function exports tunable parameters (weights/means/sigmas)  |
//| from network to contiguous array. Nothing is guaranteed about    |
//| array format, the only thing you can count for is that           |
//| MLPImportTunableParameters() will be able to parse it.           |
//| It is intended for fast copying of states between network and    |
//| backup array                                                     |
//| INPUT PARAMETERS:                                                |
//|   Network     -  source, must be correctly initialized           |
//|   P           -  array to use. If its size is enough to store    |
//|                  data, it is reused.                             |
//| OUTPUT PARAMETERS:                                               |
//|   P           -  array which stores network parameters, resized  |
//|                  if needed                                       |
//|   PCount      -  number of parameters stored in array.           |
//+------------------------------------------------------------------+
void CMLPBase::MLPExportTunableParameters(CMultilayerPerceptron &network,
                                          CRowDouble &p,int &pcount)
  {
//--- create variables
   int k=0;
   int nin=0;
   int nout=0;
   int wcount=0;
   pcount=0;
//--- check
   if(!CAp::Assert(network.m_structinfo.Size()>0 && network.m_structinfo.Size()>=network.m_structinfo[0],__FUNCTION__": Network is uninitialized"))
      return;
   MLPProperties(network,nin,nout,wcount);
   if(MLPIsSoftMax(network))
     {
      pcount=wcount+2*nin;
      CApServ::RVectorSetLengthAtLeast(p,pcount);
      for(int i=0; i<wcount; i++)
         p.Set(i,network.m_weights[i]);
      k=wcount;
      for(int i=0; i<nin; i++)
        {
         p.Set(k,network.m_columnmeans[i]);
         p.Set(k+1,network.m_columnsigmas[i]);
         k+=2;
        }
     }
   else
     {
      pcount=wcount+2*(nin+nout);
      CApServ::RVectorSetLengthAtLeast(p,pcount);
      for(int i=0; i<wcount; i++)
         p.Set(i,network.m_weights[i]);
      k=wcount;
      for(int i=0; i<nin+nout; i++)
        {
         p.Set(k,network.m_columnmeans[i]);
         p.Set(k+1,network.m_columnsigmas[i]);
         k+=2;
        }
     }
  }
//+------------------------------------------------------------------+
//| This function imports tunable parameters (weights/means/sigmas)  |
//| which were exported by MLPExportTunableParameters().             |
//| It is intended for fast copying of states between network and    |
//| backup array                                                     |
//| INPUT PARAMETERS:                                                |
//|   Network        -  target:                                      |
//|                     * must be correctly initialized              |
//|                     * must have same geometry as network used    |
//|                       to export params                           |
//|   P              -  array with parameters                        |
//+------------------------------------------------------------------+
void CMLPBase::MLPImportTunableParameters(CMultilayerPerceptron &network,
                                          CRowDouble &p)
  {
//--- create variables
   int i=0;
   int k=0;
   int nin=0;
   int nout=0;
   int wcount=0;
//--- check
   if(!CAp::Assert(network.m_structinfo.Size()>0 && network.m_structinfo.Size()>=network.m_structinfo[0],__FUNCTION__": Network is uninitialized"))
      return;
   MLPProperties(network,nin,nout,wcount);
   if(MLPIsSoftMax(network))
     {
      for(i=0; i<wcount; i++)
         network.m_weights.Set(i,p[i]);
      k=wcount;
      for(i=0; i<nin; i++)
        {
         network.m_columnmeans.Set(i,p[k]);
         network.m_columnsigmas.Set(i,p[k+1]);
         k+=2;
        }
     }
   else
     {
      for(i=0; i<wcount; i++)
         network.m_weights.Set(i,p[i]);
      k=wcount;
      for(i=0; i<nin+nout; i++)
        {
         network.m_columnmeans.Set(i,p[k]);
         network.m_columnsigmas.Set(i,p[k+1]);
         k+=2;
        }
     }
  }
//+------------------------------------------------------------------+
//| Serialization of MultiLayerPerceptron strucure                   |
//| INPUT PARAMETERS:                                                |
//|     Network -   original                                         |
//| OUTPUT PARAMETERS:                                               |
//|     RA      -   array of real numbers which stores network,      |
//|                 array[0..RLen-1]                                 |
//|     RLen    -   RA lenght                                        |
//+------------------------------------------------------------------+
void CMLPBase::MLPSerializeOld(CMultilayerPerceptron &network,
                               double &ra[],int &rlen)
  {
   CRowDouble RA;
   MLPSerializeOld(network,ra,rlen);
   RA.ToArray(ra);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CMLPBase::MLPSerializeOld(CMultilayerPerceptron &network,
                               CRowDouble &ra,int &rlen)
  {
//--- create variables
   int ssize=network.m_structinfo[0];
   int nin=network.m_structinfo[1];
   int nout=network.m_structinfo[2];
   int ntotal=network.m_structinfo[3];
   int wcount=network.m_structinfo[4];
   int sigmalen=0;
   int offs=0;
   int i=0;
   int i_=0;
   int i1_=0;
//--- check
   if(MLPIsSoftMax(network))
      sigmalen=nin;
   else
      sigmalen=nin+nout;
//---  RA format:
//---      LEN         DESRC.
//---      1           RLen
//---      1           version (MLPVNum)
//---      1           StructInfo size
//---      SSize       StructInfo
//---      WCount      Weights
//---      SigmaLen    ColumnMeans
//---      SigmaLen    ColumnSigmas
   rlen=3+ssize+wcount+2*sigmalen;
//--- allocation
   ra.Resize(rlen);
//--- change values
   ra.Set(0,rlen);
   ra.Set(1,m_mlpvnum);
   ra.Set(2,ssize);
//--- calculation
   offs=3;
   for(i=0; i<ssize; i++)
      ra.Set(offs+i,network.m_structinfo[i]);
//--- calculation
   offs+=ssize;
   for(i=0; i<wcount; i++)
      ra.Set(offs+i,network.m_weights[i]);
//--- calculation
   offs+=wcount;
   for(i=0; i<sigmalen; i++)
      ra.Set(offs+i,network.m_columnmeans[i]);
//--- calculation
   offs+=sigmalen;
   for(i=0; i<sigmalen; i++)
      ra.Set(offs+i,network.m_columnsigmas[i]);
  }
//+------------------------------------------------------------------+
//| Unserialization of MultiLayerPerceptron strucure                 |
//| INPUT PARAMETERS:                                                |
//|     RA      -   real array which stores network                  |
//| OUTPUT PARAMETERS:                                               |
//|     Network -   restored network                                 |
//+------------------------------------------------------------------+
void CMLPBase::MLPUnserializeOld(double &ra[],CMultilayerPerceptron &network)
  {
   CRowDouble RA=ra;
   MLPUnserializeOld(RA,network);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CMLPBase::MLPUnserializeOld(CRowDouble &ra,CMultilayerPerceptron &network)
  {
//--- create variables
   int i=0;
   int ssize=0;
   int ntotal=0;
   int nin=0;
   int nout=0;
   int wcount=0;
   int sigmalen=0;
   int offs=0;
   int i_=0;
   int i1_=0;
//--- check
   if(!CAp::Assert((int)MathRound(ra[1])==m_mlpvnum,__FUNCTION__+": incorrect array!"))
      return;
//--- Unload StructInfo from IA
   offs=3;
   ssize=(int)MathRound(ra[2]);
//--- allocation
   network.m_structinfo.Resize(ssize);
   for(i=0; i<ssize; i++)
      network.m_structinfo.Set(i,(int)MathRound(ra[offs+i]));
   offs+=ssize;
//--- Unload info from StructInfo
   ssize=network.m_structinfo[0];
   nin=network.m_structinfo[1];
   nout=network.m_structinfo[2];
   ntotal=network.m_structinfo[3];
   wcount=network.m_structinfo[4];
//--- check
   if(network.m_structinfo[6]==0)
      sigmalen=nin+nout;
   else
      sigmalen=nin;
//--- Allocate space for other fields
   network.m_weights.Resize(wcount);
   network.m_columnmeans.Resize(sigmalen);
   network.m_columnsigmas.Resize(sigmalen);
   network.m_neurons.Resize(ntotal);
   network.m_nwbuf.Resize(MathMax(wcount,2*nout));
   network.m_dfdnet.Resize(ntotal);
   network.m_x.Resize(nin);
   network.m_y.Resize(nout);
   network.m_derror.Resize(ntotal);
//--- Copy parameters from RA
   for(i=0; i<wcount; i++)
      network.m_weights.Set(i_,ra[i+offs]);
//--- calculation
   offs+=wcount;
   for(i=0; i_<sigmalen; i++)
      network.m_columnmeans.Set(i,ra[i+offs]);
//--- calculation
   offs+=sigmalen;
   for(i=0; i<sigmalen; i++)
      network.m_columnsigmas.Set(i,ra[i+offs]);
  }
//+------------------------------------------------------------------+
//| Randomization of neural network weights                          |
//+------------------------------------------------------------------+
void CMLPBase::MLPRandomize(CMultilayerPerceptron &network)
  {
//--- create variables
   int    nin=0;
   int    nout=0;
   int    wcount=0;
   int    ntotal=0;
   int    istart=0;
   CHighQualityRandState r;
   int    entrysize=0;
   int    entryoffs=0;
   int    neuronidx=0;
   int    neurontype=0;
   double vmean=0;
   double vvar=0;
   int    i=0;
   int    n1=0;
   int    n2=0;
   double desiredsigma=0;
   int    montecarlocnt=0;
   double ef=0;
   double ef2=0;
   double v=0;
   double wscale=0;
   CHighQualityRand::HQRndRandomize(r);

   MLPProperties(network,nin,nout,wcount);
   ntotal=network.m_structinfo[3];
   istart=network.m_structinfo[5];
   desiredsigma=0.5;
   montecarlocnt=20;
//--- Stage 1:
//--- * Network.Weights is filled by standard deviation of weights
//--- * default values: sigma=1
   network.m_weights.Fill(1.0);
//--- Stage 2:
//--- * assume that input neurons have zero mean and unit standard deviation
//--- * assume that constant neurons have zero standard deviation
//--- * perform forward pass along neurons
//--- * for each non-input non-constant neuron:
//---   * calculate mean and standard deviation of neuron's output
//---     assuming that we know means/deviations of neurons which feed it
//---     and assuming that weights has unit variance and zero mean.
//--- * for each nonlinear neuron additionally we perform backward pass:
//---   * scale variances of weights which feed it in such way that neuron's
//---     input has unit standard deviation
//--- NOTE: this algorithm assumes that each connection feeds at most one
//---       non-linear neuron. This assumption can be incorrect in upcoming
//---       architectures with strong neurons. However, algorithm should
//---       work smoothly even in this case.
//--- During this stage we use Network.RndBuf, which is grouped into NTotal
//--- entries, each of them having following format:
//--- Buf[Offset+0]        mean value of neuron's output
//--- Buf[Offset+1]        standard deviation of neuron's output
   entrysize=2;
   CApServ::RVectorSetLengthAtLeast(network.m_rndbuf,entrysize*ntotal);
   for(neuronidx=0; neuronidx<ntotal; neuronidx++)
     {
      neurontype=network.m_structinfo[istart+neuronidx*m_nfieldwidth];
      entryoffs=entrysize*neuronidx;
      switch(neurontype)
        {
         case -2:
            //--- Input neuron: zero mean, unit variance.
            network.m_rndbuf.Set(entryoffs,0.0);
            network.m_rndbuf.Set(entryoffs+1,1.0);
            break;
         case -3:
            //--- "-1" neuron: mean=-1, zero variance.
            network.m_rndbuf.Set(entryoffs,-1.0);
            network.m_rndbuf.Set(entryoffs+1,0.0);
            break;
         case -4:
            //--- "0" neuron: mean=0, zero variance.
            network.m_rndbuf.Set(entryoffs,0.0);
            network.m_rndbuf.Set(entryoffs+1,0.0);
            break;
         case 0:
            //--- Adaptive summator neuron:
            //--- * calculate its mean and variance.
            //--- * we assume that weights of this neuron have unit variance and zero mean.
            //--- * thus, neuron's output is always have zero mean
            //--- * as for variance, it is a bit more interesting:
            //---   * let n[i] is i-th input neuron
            //---   * let w[i] is i-th weight
            //---   * we assume that n[i] and w[i] are independently distributed
            //---   * Var(n0*w0+n1*w1+...) = Var(n0*w0)+Var(n1*w1)+...
            //---   * Var(X*Y) = mean(X)^2*Var(Y) + mean(Y)^2*Var(X) + Var(X)*Var(Y)
            //---   * mean(w[i])=0, var(w[i])=1
            //---   * Var(n[i]*w[i]) = mean(n[i])^2 + Var(n[i])
            n1=network.m_structinfo[istart+neuronidx*m_nfieldwidth+2];
            n2=n1+network.m_structinfo[istart+neuronidx*m_nfieldwidth+1];
            vmean=0.0;
            vvar=0.0;
            for(i=n1; i<n2; i++)
               vvar+=CMath::Sqr(network.m_rndbuf[entrysize*i])+CMath::Sqr(network.m_rndbuf[entrysize*i+1]);
            network.m_rndbuf.Set(entryoffs,vmean);
            network.m_rndbuf.Set(entryoffs+1,MathSqrt(vvar));
            break;
         case -5:
            //--- Linear activation function
            i=network.m_structinfo[istart+neuronidx*m_nfieldwidth+2];
            vmean=network.m_rndbuf[entrysize*i];
            vvar=CMath::Sqr(network.m_rndbuf[entrysize*i+1]);
            if(vvar>0.0)
               wscale=desiredsigma/MathSqrt(vvar);
            else
               wscale=1.0;
            RandomizeBackwardPass(network,i,wscale);
            network.m_rndbuf.Set(entryoffs,vmean*wscale);
            network.m_rndbuf.Set(entryoffs+1,desiredsigma);
            break;
         default:
            if(neurontype>0)
              {
               //--- Nonlinear activation function:
               //--- * scale its inputs
               //--- * estimate mean/sigma of its output using Monte-Carlo method
               //---   (we simulate different inputs with unit deviation and
               //---   sample activation function output on such inputs)
               i=network.m_structinfo[istart+neuronidx*m_nfieldwidth+2];
               vmean=network.m_rndbuf[entrysize*i];
               vvar=CMath::Sqr(network.m_rndbuf[entrysize*i+1]);
               if(vvar>0.0)
                  wscale=desiredsigma/MathSqrt(vvar);
               else
                  wscale=1.0;
               RandomizeBackwardPass(network,i,wscale);
               ef=0.0;
               ef2=0.0;
               vmean=vmean*wscale;
               for(i=0; i<montecarlocnt; i++)
                 {
                  v=vmean+desiredsigma*CHighQualityRand::HQRndNormal(r);
                  ef+=v;
                  ef2+=v*v;
                 }
               ef=ef/montecarlocnt;
               ef2=ef2/montecarlocnt;
               network.m_rndbuf.Set(entryoffs,ef);
               network.m_rndbuf.Set(entryoffs+1,MathMax(ef2-ef*ef,0.0));
              }
            else
              {
               CAp::Assert(false,__FUNCTION__": unexpected neuron type");
               return;
              }
            break;
        }
     }
//--- Stage 3: generate weights.
   for(i=0; i<wcount; i++)
      network.m_weights.Mul(i,CHighQualityRand::HQRndNormal(r));
  }
//+------------------------------------------------------------------+
//| Randomization of neural network weights and standartisator       |
//+------------------------------------------------------------------+
void CMLPBase::MLPRandomizeFull(CMultilayerPerceptron &network)
  {
//--- create variables
   int i=0;
   int nin=0;
   int nout=0;
   int wcount=0;
   int ntotal=0;
   int istart=0;
   int offs=0;
   int ntype=0;
//--- function call
   MLPProperties(network,nin,nout,wcount);
//--- initialization
   ntotal=network.m_structinfo[3];
   istart=network.m_structinfo[5];
//--- Process network
   MLPRandomize(network);
   for(i=0; i<nin; i++)
     {
      network.m_columnmeans.Set(i,CMath::RandomReal()-0.5);
      network.m_columnsigmas.Set(i,CMath::RandomReal()+0.5);
     }
//--- check
   if(!MLPIsSoftMax(network))
     {
      for(i=0; i<nout; i++)
        {
         offs=istart+(ntotal-nout+i)*m_nfieldwidth;
         ntype=network.m_structinfo[offs];
         //--- check
         if(ntype==0)
           {
            //--- Shifts are changed only for linear outputs neurons
            network.m_columnmeans.Set(nin+i,2*CMath::RandomReal()-1);
           }
         //--- check
         if(ntype==0 || ntype==3)
           {
            //--- Scales are changed only for linear or bounded outputs neurons.
            //--- Note that scale randomization preserves sign.
            network.m_columnsigmas.Set(nin+i,MathSign(network.m_columnsigmas[nin+i])*(1.5*CMath::RandomReal()+0.5));
           }
        }
     }
  }
//+------------------------------------------------------------------+
//| Internal subroutine.                                             |
//+------------------------------------------------------------------+
void CMLPBase::MLPInitPreprocessor(CMultilayerPerceptron &network,
                                   CMatrixDouble &xy,const int ssize)
  {
//--- create variables
   int    i=0;
   int    j=0;
   int    nin=0;
   int    nout=0;
   int    wcount=0;
   int    ntotal=0;
   int    istart=0;
   int    offs=0;
   int    ntype=0;
   int    jmax=0;
   double s=0;
//--- creating arrays
   vector<double> means;
   vector<double> sigmas;
//--- function call
   MLPProperties(network,nin,nout,wcount);
//--- initialization
   ntotal=network.m_structinfo[3];
   istart=network.m_structinfo[5];
//--- allocation
   if(ssize>0)
     {
      matrix<double> XY=xy.ToMatrix();
      XY.Resize(ssize,nin+nout);
      means=XY.Mean(0);
      sigmas=XY.Std(0);
     }
   else
     {
      if(MLPIsSoftMax(network))
         jmax=nin+1;
      else
         jmax=nin+nout;
      means=vector<double>::Zeros(jmax);
      sigmas=vector<double>::Ones(jmax);
     }
//--- Inputs
   for(i=0; i<nin; i++)
     {
      network.m_columnmeans.Set(i,means[i]);
      network.m_columnsigmas.Set(i,(sigmas[i]!=0?sigmas[i]:1));
     }
//--- Outputs
   if(!MLPIsSoftMax(network))
     {
      for(i=0; i<nout; i++)
        {
         offs=istart+(ntotal-nout+i)*m_nfieldwidth;
         ntype=network.m_structinfo[offs];
         //--- Linear outputs
         if(ntype==0)
           {
            network.m_columnmeans.Set(nin+i,means[nin+i]);
            network.m_columnsigmas.Set(nin+i,(sigmas[nin+i]!=0?sigmas[nin+i]:1));
           }
         //--- Bounded outputs (half-interval)
         if(ntype==3)
           {
            s=means[nin+i]-network.m_columnmeans[nin+i];
            //--- check
            if(s==0.0)
               s=MathSign(network.m_columnsigmas[nin+i]);
            //--- check
            if(s==0.0)
               s=1.0;
            //--- change value
            network.m_columnsigmas.Set(nin+i,MathSign(network.m_columnsigmas[nin+i])*MathAbs(s));
            //--- check
            if(network.m_columnsigmas[nin+i]==0.0)
               network.m_columnsigmas.Set(nin+i,1);
           }
        }
     }
  }
//+------------------------------------------------------------------+
//| Internal subroutine.                                             |
//| Initialization for preprocessor based on a sample.               |
//| INPUT                                                            |
//|   Network  -  initialized neural network;                        |
//|   XY       -  sample, given by sparse matrix;                    |
//|   SSize    -  sample size.                                       |
//| OUTPUT                                                           |
//|   Network  -  neural network with initialised preprocessor.      |
//+------------------------------------------------------------------+
void CMLPBase::MLPInitPreprocessorSparse(CMultilayerPerceptron &network,
                                         CSparseMatrix &xy,int ssize)
  {
//--- create variables
   int    jmax=0;
   int    nin=0;
   int    nout=0;
   int    wcount=0;
   int    ntotal=0;
   int    istart=0;
   int    offs=0;
   int    ntype=0;
   double s=0;
   int    i=0;
   int    j=0;
//--- create arrays
   vector<double> means;
   vector<double> sigmas;
   CMatrixDouble dense_xy;

   MLPProperties(network,nin,nout,wcount);
   ntotal=network.m_structinfo[3];
   istart=network.m_structinfo[5];
//--- Means/Sigmas
   if(MLPIsSoftMax(network))
      jmax=nin+1;
   else
      jmax=nin+nout;
   dense_xy=matrix<double>::Zeros(ssize,jmax);
   for(i=0; i<ssize; i++)
     {
      CSparse::SparseGetRow(xy,i,network.m_xyrow);
      dense_xy.Row(i,network.m_xyrow);
     }
   if(ssize>0)
     {
      means=dense_xy.Mean(0);
      sigmas=dense_xy.Std(0);
     }
   else
     {
      means=vector<double>::Zeros(jmax);
      sigmas=vector<double>::Ones(jmax);
     }
//--- Inputs
   for(i=0; i<nin; i++)
     {
      network.m_columnmeans.Set(i,means[i]);
      network.m_columnsigmas.Set(i,(sigmas[i]!=0?sigmas[i]:1));
     }
//--- Outputs
   if(!MLPIsSoftMax(network))
     {
      for(i=0; i<nout; i++)
        {
         offs=istart+(ntotal-nout+i)*m_nfieldwidth;
         ntype=network.m_structinfo[offs];
         //--- Linear outputs
         if(ntype==0)
           {
            network.m_columnmeans.Set(nin+i,means[nin+i]);
            network.m_columnsigmas.Set(nin+i,(sigmas[nin+i]!=0.0?sigmas[nin+i]:1.0));
           }
         //--- Bounded outputs (half-interval)
         if(ntype==3)
           {
            s=means[nin+i]-network.m_columnmeans[nin+i];
            if(s==0.0)
               s=MathSign(network.m_columnsigmas[nin+i]);
            if(s==0.0)
               s=1.0;
            s=MathSign(network.m_columnsigmas[nin+i])*MathAbs(s);
            network.m_columnsigmas.Set(nin+i,(s!=0.0?s:1.0));
           }
        }
     }
  }
//+------------------------------------------------------------------+
//| Internal subroutine.                                             |
//| Initialization for preprocessor based on a subsample.            |
//| INPUT PARAMETERS:                                                |
//|   Network     -  network initialized with one of the network     |
//|                  creation funcs                                  |
//|   XY          -  original dataset; one sample = one row;         |
//|                  first NIn columns contain inputs,               |
//|                  next NOut columns - desired outputs.            |
//|   SetSize     -  real size of XY, SetSize>=0;                    |
//|   Idx         -  subset of SubsetSize elements, array[SubsetSize]|
//|      * Idx[I] stores row index in the original dataset which is  |
//|        given by XY. Gradient is calculated with respect to rows  |
//|        whose indexes are stored in Idx[].                        |
//|      * Idx[]  must store correct indexes; this function  throws  |
//|        an  exception  in  case  incorrect index (less than 0 or  |
//|        larger than rows(XY)) is given                            |
//|      * Idx[]  may  store  indexes  in  any  order and even with  |
//|        repetitions.                                              |
//|   SubsetSize  -  number of elements in Idx[] array.              |
//| OUTPUT:                                                          |
//|   Network     -  neural network with initialised preprocessor.   |
//| NOTE: when SubsetSize<0 is used full dataset by call             |
//|       MLPInitPreprocessor function.                              |
//+------------------------------------------------------------------+
void CMLPBase::MLPInitPreprocessorSubset(CMultilayerPerceptron &network,
                                         CMatrixDouble &xy,int setsize,
                                         CRowInt &idx,int subsetsize)
  {
//--- create variables
   int    jmax=0;
   int    nin=0;
   int    nout=0;
   int    wcount=0;
   int    ntotal=0;
   int    istart=0;
   int    offs=0;
   int    ntype=0;
   double s=0;
   int    npoints=0;
   int    i=0;
   int    j=0;
//--- create arrays
   vector<double> means;
   vector<double> sigmas;
//--- check
   if(!CAp::Assert(setsize>=0,__FUNCTION__": SetSize<0"))
      return;
   if(subsetsize<0)
     {
      MLPInitPreprocessor(network,xy,setsize);
      return;
     }
//--- check
   if(!CAp::Assert(subsetsize<=idx.Size(),__FUNCTION__": SubsetSize>Length(Idx)"))
      return;
   npoints=setsize;
   for(i=0; i<subsetsize; i++)
     {
      if(!CAp::Assert(idx[i]>=0,__FUNCTION__": incorrect index of XY row(Idx[I]<0)"))
         return;
      if(!CAp::Assert(idx[i]<npoints,__FUNCTION__": incorrect index of XY row(Idx[I]>=Rows(XY))"))
         return;
     }
//--- get Network info
   MLPProperties(network,nin,nout,wcount);
   ntotal=network.m_structinfo[3];
   istart=network.m_structinfo[5];
//--- Means/Sigmas
   if(MLPIsSoftMax(network))
      jmax=nin;
   else
      jmax=nin+nout;
   means=vector<double>::Zeros(jmax);
   sigmas=means;
   for(i=0; i<subsetsize; i++)
     {
      for(j=0; j<jmax; j++)
         means[j]+=xy.Get(idx[i],j);
     }
   means=means/subsetsize;
   for(i=0; i<subsetsize; i++)
     {
      for(j=0; j<jmax; j++)
         sigmas[j]+=CMath::Sqr(xy.Get(idx[i],j)-means[j]);
     }
   sigmas=MathSqrt(sigmas/subsetsize);
//--- Inputs
   for(i=0; i<nin; i++)
     {
      network.m_columnmeans.Set(i,means[i]);
      network.m_columnsigmas.Set(i,(sigmas[i]!=0?sigmas[i]:1));
     }
//--- Outputs
   if(!MLPIsSoftMax(network))
     {
      for(i=0; i<nout; i++)
        {
         offs=istart+(ntotal-nout+i)*m_nfieldwidth;
         ntype=network.m_structinfo[offs];
         //--- Linear outputs
         if(ntype==0)
           {
            network.m_columnmeans.Set(nin+i,means[nin+i]);
            network.m_columnsigmas.Set(nin+i,(sigmas[nin+i]!=0.0?sigmas[nin+i]:1.0));
           }
         //--- Bounded outputs (half-interval)
         if(ntype==3)
           {
            s=means[nin+i]-network.m_columnmeans[nin+i];
            if(s==0.0)
               s=MathSign(network.m_columnsigmas[nin+i]);
            if(s==0.0)
               s=1.0;
            s=MathSign(network.m_columnsigmas[nin+i])*MathAbs(s);
            network.m_columnsigmas.Set(nin+i,(s!=0.0?s:1));
           }
        }
     }
  }
//+------------------------------------------------------------------+
//| Internal subroutine.                                             |
//| Initialization for preprocessor based on a subsample.            |
//| INPUT PARAMETERS:                                                |
//|   Network  -  network initialized with one of the network        |
//|               creation funcs                                     |
//|   XY       -  original dataset, given by sparse matrix;          |
//|               one sample = one row;                              |
//|               first NIn columns contain inputs,                  |
//|               next NOut columns - desired outputs.               |
//|   SetSize  -  real size of XY, SetSize>=0;                       |
//|   Idx      -  subset of SubsetSize elements, array[SubsetSize]:  |
//|      * Idx[I] stores row index in the original dataset which is  |
//|        given by XY. Gradient is calculated with respect to rows  |
//|        whose indexes are stored in Idx[].                        |
//|      * Idx[]  must store correct indexes; this function  throws  |
//|        an  exception  in  case  incorrect index (less than 0 or  |
//|        larger than rows(XY)) is given                            |
//|      * Idx[]  may  store  indexes  in  any  order and even with  |
//|        repetitions.                                              |
//|   SubsetSize- number of elements in Idx[] array.                 |
//| OUTPUT:                                                          |
//|   Network  -  neural network with initialised preprocessor.      |
//| NOTE: when SubsetSize<0 is used full dataset by call             |
//|       MLPInitPreprocessorSparse function.                        |
//+------------------------------------------------------------------+
void CMLPBase::MLPInitPreprocessorSparseSubset(CMultilayerPerceptron &network,
                                               CSparseMatrix &xy,int setsize,
                                               CRowInt &idx,int subsetsize)
  {
//--- create variables
   int    jmax=0;
   int    nin=0;
   int    nout=0;
   int    wcount=0;
   int    ntotal=0;
   int    istart=0;
   int    offs=0;
   int    ntype=0;
   double s=0;
   int    npoints=0;
   int    i=0;
   int    j=0;
//--- create arrays
   vector<double> means;
   vector<double> sigmas;
//--- check
   if(!CAp::Assert(setsize>=0,__FUNCTION__": SetSize<0"))
      return;
   if(subsetsize<0)
     {
      MLPInitPreprocessorSparse(network,xy,setsize);
      return;
     }
   if(!CAp::Assert(subsetsize<=idx.Size(),__FUNCTION__": SubsetSize>Length(Idx)"))
      return;
   npoints=setsize;
   for(i=0; i<subsetsize; i++)
     {
      if(!CAp::Assert(idx[i]>=0,__FUNCTION__": incorrect index of XY row(Idx[I]<0)"))
         return;
      if(!CAp::Assert(idx[i]<npoints,__FUNCTION__": incorrect index of XY row(Idx[I]>=Rows(XY))"))
         return;
     }
//--- get Network info
   MLPProperties(network,nin,nout,wcount);
   ntotal=network.m_structinfo[3];
   istart=network.m_structinfo[5];
//
//--- Means/Sigmas
//
   if(MLPIsSoftMax(network))
      jmax=nin;
   else
      jmax=nin+nout;
   means=vector<double>::Zeros(jmax);
   sigmas=means;
   for(i=0; i<subsetsize; i++)
     {
      CSparse::SparseGetRow(xy,idx[i],network.m_xyrow);
      means+=network.m_xyrow.ToVector();
     }
   means=means/subsetsize;
   for(i=0; i<=subsetsize-1; i++)
     {
      CSparse::SparseGetRow(xy,idx[i],network.m_xyrow);
      sigmas+=MathPow(network.m_xyrow-means+0,2.0);
     }
   sigmas=MathSqrt(sigmas/subsetsize);
//--- Inputs
   for(i=0; i<nin; i++)
     {
      network.m_columnmeans.Set(i,means[i]);
      network.m_columnsigmas.Set(i,(sigmas[i]!=0?sigmas[i]:1));
     }
//--- Outputs
   if(!MLPIsSoftMax(network))
     {
      for(i=0; i<nout; i++)
        {
         offs=istart+(ntotal-nout+i)*m_nfieldwidth;
         ntype=network.m_structinfo[offs];
         //--- Linear outputs
         if(ntype==0)
           {
            network.m_columnmeans.Set(nin+i,means[nin+i]);
            network.m_columnsigmas.Set(nin+i,(sigmas[nin+i]!=0.0?sigmas[nin+i]:1.0));
           }
         //--- Bounded outputs (half-interval)
         if(ntype==3)
           {
            s=means[nin+i]-network.m_columnmeans[nin+i];
            if(s==0.0)
               s=MathSign(network.m_columnsigmas[nin+i]);
            if(s==0.0)
               s=1.0;
            s=MathSign(network.m_columnsigmas[nin+i])*MathAbs(s);
            network.m_columnsigmas.Set(nin+i,(s!=0?s:1));
           }
        }
     }
  }
//+------------------------------------------------------------------+
//| Returns information about initialized network: number of inputs, |
//| outputs, weights.                                                |
//+------------------------------------------------------------------+
void CMLPBase::MLPProperties(CMultilayerPerceptron &network,int &nin,
                             int &nout,int &wcount)
  {
//--- change values
   nin=network.m_structinfo[1];
   nout=network.m_structinfo[2];
   wcount=network.m_structinfo[4];
  }
//+------------------------------------------------------------------+
//| Returns number of "internal", low-level neurons in the network   |
//| (one  which is stored in StructInfo).                            |
//+------------------------------------------------------------------+
int CMLPBase::MLPNTotal(CMultilayerPerceptron &network)
  {
   return(network.m_structinfo[3]);
  }
//+------------------------------------------------------------------+
//| Returns number of inputs.                                        |
//+------------------------------------------------------------------+
int CMLPBase::MLPGetInputsCount(CMultilayerPerceptron &network)
  {
   return(network.m_structinfo[1]);
  }
//+------------------------------------------------------------------+
//| Returns number of outputs.                                       |
//+------------------------------------------------------------------+
int CMLPBase::MLPGetOutputsCount(CMultilayerPerceptron &network)
  {
   return(network.m_structinfo[2]);
  }
//+------------------------------------------------------------------+
//| Returns number of weights.                                       |
//+------------------------------------------------------------------+
int CMLPBase::MLPGetWeightsCount(CMultilayerPerceptron &network)
  {
   return(network.m_structinfo[4]);
  }
//+------------------------------------------------------------------+
//| Tells whether network is SOFTMAX-normalized (i.m_e. classifier)  |
//| or not.                                                          |
//+------------------------------------------------------------------+
bool CMLPBase::MLPIsSoftMax(CMultilayerPerceptron &network)
  {
//--- check
   if(network.m_structinfo[6]==1)
      return(true);
//--- return result
   return(false);
  }
//+------------------------------------------------------------------+
//| This function returns total number of layers (including input,   |
//| hidden and output layers).                                       |
//+------------------------------------------------------------------+
int CMLPBase::MLPGetLayersCount(CMultilayerPerceptron &network)
  {
//--- return result
   return(CAp::Len(network.m_hllayersizes));
  }
//+------------------------------------------------------------------+
//| This function returns size of K-th layer.                        |
//| K=0 corresponds to input layer, K=CNT-1 corresponds to output    |
//| layer.                                                           |
//| Size of the output layer is always equal to the number of        |
//| outputs, although when we have softmax-normalized network, last  |
//| neuron doesn't have any connections - it is just zero.           |
//+------------------------------------------------------------------+
int CMLPBase::MLPGetLayerSize(CMultilayerPerceptron &network,
                              const int k)
  {
//--- check
   if(!CAp::Assert(k>=0 && k<CAp::Len(network.m_hllayersizes),__FUNCTION__+": incorrect layer index"))
      return(-1);
//--- return result
   return(network.m_hllayersizes[k]);
  }
//+------------------------------------------------------------------+
//| This function returns offset/scaling coefficients for I-th input |
//| of the network.                                                  |
//| INPUT PARAMETERS:                                                |
//|     Network     -   network                                      |
//|     I           -   input index                                  |
//| OUTPUT PARAMETERS:                                               |
//|     Mean        -   mean term                                    |
//|     Sigma       -   sigma term,guaranteed to be nonzero.         |
//| I-th input is passed through linear transformation               |
//|     IN[i]=(IN[i]-Mean)/Sigma                                     |
//| before feeding to the network                                    |
//+------------------------------------------------------------------+
void CMLPBase::MLPGetInputScaling(CMultilayerPerceptron &network,
                                  const int i,double &mean,
                                  double &sigma)
  {
//--- check
   if(!CAp::Assert(i>=0 && i<network.m_hllayersizes[0],__FUNCTION__+": incorrect (nonexistent) I"))
     {
      mean=0;
      sigma=0;
      return;
     }
//--- change values
   mean=network.m_columnmeans[i];
   sigma=network.m_columnsigmas[i];
//--- check
   if(sigma==0.0)
      sigma=1;
  }
//+------------------------------------------------------------------+
//| This function returns offset/scaling coefficients for I-th output|
//| of the network.                                                  |
//| INPUT PARAMETERS:                                                |
//|     Network     -   network                                      |
//|     I           -   input index                                  |
//| OUTPUT PARAMETERS:                                               |
//|     Mean        -   mean term                                    |
//|     Sigma       -   sigma term, guaranteed to be nonzero.        |
//| I-th output is passed through linear transformation              |
//|     OUT[i] = OUT[i]*Sigma+Mean                                   |
//| before returning it to user. In case we have SOFTMAX-normalized  |
//| network, we return (Mean,Sigma)=(0.0,1.0).                       |
//+------------------------------------------------------------------+
void CMLPBase::MLPGetOutputScaling(CMultilayerPerceptron &network,
                                   const int i,double &mean,
                                   double &sigma)
  {
//--- check
   if(!CAp::Assert(i>=0 && i<network.m_hllayersizes[CAp::Len(network.m_hllayersizes)-1],__FUNCTION__+": incorrect (nonexistent) I"))
     {
      mean=0;
      sigma=0;
      return;
     }
//--- check
   if(network.m_structinfo[6]==1)
     {
      //--- change values
      mean=0;
      sigma=1;
     }
   else
     {
      //--- change values
      mean=network.m_columnmeans[network.m_hllayersizes[0]+i];
      sigma=network.m_columnsigmas[network.m_hllayersizes[0]+i];
     }
  }
//+------------------------------------------------------------------+
//| This function returns information about Ith neuron of Kth layer  |
//| INPUT PARAMETERS:                                                |
//|     Network     -   network                                      |
//|     K           -   layer index                                  |
//|     I           -   neuron index (within layer)                  |
//| OUTPUT PARAMETERS:                                               |
//|     FKind       -   activation function type (used by            |
//|                     MLPActivationFunction()) this value is zero  |
//|                     for input or linear neurons                  |
//|     Threshold   -   also called offset, bias                     |
//|                     zero for input neurons                       |
//| NOTE: this function throws exception if layer or neuron with     |
//| given index do not exists.                                       |
//+------------------------------------------------------------------+
void CMLPBase::MLPGetNeuronInfo(CMultilayerPerceptron &network,
                                const int k,const int i,int &fkind,
                                double &threshold)
  {
//--- create variables
   int ncnt=0;
   int istart=0;
   int highlevelidx=0;
   int activationoffset=0;
//--- initialization
   fkind=0;
   threshold=0;
   ncnt=network.m_hlneurons.Size()/m_hlm_nfieldwidth;
   istart=network.m_structinfo[5];
//--- search
   network.m_integerbuf.Set(0,k);
   network.m_integerbuf.Set(1,i);
//--- function call
   highlevelidx=CApServ::RecSearch(network.m_hlneurons,m_hlm_nfieldwidth,2,0,ncnt,network.m_integerbuf);
//--- check
   if(!CAp::Assert(highlevelidx>=0,__FUNCTION__+": incorrect (nonexistent) layer or neuron index"))
      return;
//--- 1. find offset of the activation function record in the
   if(network.m_hlneurons[highlevelidx*m_hlm_nfieldwidth+2]>=0)
     {
      activationoffset=istart+network.m_hlneurons[highlevelidx*m_hlm_nfieldwidth+2]*m_nfieldwidth;
      fkind=network.m_structinfo[activationoffset+0];
     }
   else
      fkind=0;
//--- check
   if(network.m_hlneurons[highlevelidx*m_hlm_nfieldwidth+3]>=0)
      threshold=network.m_weights[network.m_hlneurons[highlevelidx*m_hlm_nfieldwidth+3]];
   else
      threshold=0;
  }
//+------------------------------------------------------------------+
//| This function returns information about connection from I0-th    |
//| neuron of K0-th layer to I1-th neuron of K1-th layer.            |
//| INPUT PARAMETERS:                                                |
//|     Network     -   network                                      |
//|     K0          -   layer index                                  |
//|     I0          -   neuron index (within layer)                  |
//|     K1          -   layer index                                  |
//|     I1          -   neuron index (within layer)                  |
//| RESULT:                                                          |
//|     connection weight (zero for non-existent connections)        |
//| This function:                                                   |
//| 1. throws exception if layer or neuron with given index do not   |
//|    exists.                                                       |
//| 2. returns zero if neurons exist, but there is no connection     |
//|    between them                                                  |
//+------------------------------------------------------------------+
double CMLPBase::MLPGetWeight(CMultilayerPerceptron &network,
                              const int k0,const int i0,
                              const int k1,const int i1)
  {
//--- create variables
   double result=0;
   int    ccnt=0;
   int    highlevelidx=0;
//--- initialization
   ccnt=CAp::Len(network.m_hlconnections)/m_hlconm_nfieldwidth;
//--- check params
   if(!CAp::Assert(k0>=0 && k0<CAp::Len(network.m_hllayersizes),__FUNCTION__+": incorrect (nonexistent) K0"))
      return(EMPTY_VALUE);
//--- check
   if(!CAp::Assert(i0>=0 && i0<network.m_hllayersizes[k0],__FUNCTION__+": incorrect (nonexistent) I0"))
      return(EMPTY_VALUE);
//--- check
   if(!CAp::Assert(k1>=0 && k1<CAp::Len(network.m_hllayersizes),__FUNCTION__+": incorrect (nonexistent) K1"))
      return(EMPTY_VALUE);
//--- check
   if(!CAp::Assert(i1>=0 && i1<network.m_hllayersizes[k1],__FUNCTION__+": incorrect (nonexistent) I1"))
      return(EMPTY_VALUE);
//--- search
   network.m_integerbuf.Set(0,k0);
   network.m_integerbuf.Set(1,i0);
   network.m_integerbuf.Set(2,k1);
   network.m_integerbuf.Set(3,i1);
//--- function call
   highlevelidx=CApServ::RecSearch(network.m_hlconnections,m_hlconm_nfieldwidth,4,0,ccnt,network.m_integerbuf);
//--- check
   if(highlevelidx>=0)
      result=network.m_weights[network.m_hlconnections[highlevelidx*m_hlconm_nfieldwidth+4]];
   else
      result=0;
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| This function sets offset/scaling coefficients for I-th input of |
//| the network.                                                     |
//| INPUT PARAMETERS:                                                |
//|     Network     -   network                                      |
//|     I           -   input index                                  |
//|     Mean        -   mean term                                    |
//|     Sigma       -   sigma term (if zero,will be replaced by 1.0) |
//| NTE: I-th input is passed through linear transformation          |
//|     IN[i]=(IN[i]-Mean)/Sigma                                     |
//| before feeding to the network. This function sets Mean and Sigma.|
//+------------------------------------------------------------------+
void CMLPBase::MLPSetInputScaling(CMultilayerPerceptron &network,
                                  const int i,const double mean,
                                  double sigma)
  {
//--- check
   if(!CAp::Assert(i>=0 && i<network.m_hllayersizes[0],__FUNCTION__+": incorrect (nonexistent) I"))
      return;
//--- check
   if(!CAp::Assert(MathIsValidNumber(mean),__FUNCTION__+": infinite or NAN Mean"))
      return;
//--- check
   if(!CAp::Assert(MathIsValidNumber(sigma),__FUNCTION__+": infinite or NAN Sigma"))
      return;
//--- check
   if(sigma==0.0)
      sigma=1;
//--- change values
   network.m_columnmeans.Set(i,mean);
   network.m_columnsigmas.Set(i,sigma);
  }
//+------------------------------------------------------------------+
//| This function sets offset/scaling coefficients for I-th output of|
//| the network.                                                     |
//| INPUT PARAMETERS:                                                |
//|     Network     -   network                                      |
//|     I           -   input index                                  |
//|     Mean        -   mean term                                    |
//|     Sigma       -   sigma term (if zero, will be replaced by 1.0)|
//| OUTPUT PARAMETERS:                                               |
//| NOTE: I-th output is passed through linear transformation        |
//|     OUT[i] = OUT[i]*Sigma+Mean                                   |
//| before returning it to user. This function sets Sigma/Mean. In   |
//| case we have SOFTMAX-normalized network, you can not set (Sigma, |
//| Mean) to anything other than(0.0,1.0) - this function will throw |
//| exception.                                                       |
//+------------------------------------------------------------------+
void CMLPBase::MLPSetOutputScaling(CMultilayerPerceptron &network,
                                   const int i,const double mean,
                                   double sigma)
  {
//--- check
   if(!CAp::Assert(i>=0 && i<network.m_hllayersizes[CAp::Len(network.m_hllayersizes)-1],__FUNCTION__+": incorrect (nonexistent) I"))
      return;
//--- check
   if(!CAp::Assert(MathIsValidNumber(mean),__FUNCTION__+": infinite or NAN Mean"))
      return;
//--- check
   if(!CAp::Assert(MathIsValidNumber(sigma),__FUNCTION__+": infinite or NAN Sigma"))
      return;
//--- check
   if(network.m_structinfo[6]==1)
     {
      //--- check
      if(!CAp::Assert(mean==0.0,__FUNCTION__+": you can not set non-zero Mean term for classifier network"))
         return;
      //--- check
      if(!CAp::Assert(sigma==1.0,__FUNCTION__+": you can not set non-unit Sigma term for classifier network"))
         return;
     }
   else
     {
      //--- check
      if(sigma==0.0)
         sigma=1;
      //--- change values
      network.m_columnmeans.Set(network.m_hllayersizes[0]+i,mean);
      network.m_columnsigmas.Set(network.m_hllayersizes[0]+i,sigma);
     }
  }
//+------------------------------------------------------------------+
//| This function modifies information about Ith neuron of Kth layer |
//| INPUT PARAMETERS:                                                |
//|     Network     -   network                                      |
//|     K           -   layer index                                  |
//|     I           -   neuron index (within layer)                  |
//|     FKind       -   activation function type (used by            |
//|                     MLPActivationFunction()) this value must be  |
//|                     zero for input neurons (you can not set      |
//|                     activation function for input neurons)       |
//|     Threshold   -   also called offset, bias                     |
//|                     this value must be zero for input neurons    |
//|                     (you can not set threshold for input neurons)|
//| NOTES:                                                           |
//| 1. this function throws exception if layer or neuron with given  |
//|    index do not exists.                                          |
//| 2. this function also throws exception when you try to set       |
//|    non-linear activation function for input neurons (any kind    |
//|    of network) or for output neurons of classifier network.      |
//| 3. this function throws exception when you try to set non-zero   |
//|    threshold for input neurons (any kind of network).            |
//+------------------------------------------------------------------+
void CMLPBase::MLPSetNeuronInfo(CMultilayerPerceptron &network,
                                const int k,const int i,
                                const int fkind,const double threshold)
  {
//--- create variables
   int ncnt=0;
   int istart=0;
   int highlevelidx=0;
   int activationoffset=0;
//--- check
   if(!CAp::Assert(MathIsValidNumber(threshold),__FUNCTION__+": infinite or NAN Threshold"))
      return;
//--- convenience vars
   ncnt=CAp::Len(network.m_hlneurons)/m_hlm_nfieldwidth;
   istart=network.m_structinfo[5];
//--- search
   network.m_integerbuf.Set(0,k);
   network.m_integerbuf.Set(1,i);
//--- function call
   highlevelidx=CApServ::RecSearch(network.m_hlneurons,m_hlm_nfieldwidth,2,0,ncnt,network.m_integerbuf);
//--- check
   if(!CAp::Assert(highlevelidx>=0,__FUNCTION__+": incorrect (nonexistent) layer or neuron index"))
      return;
//--- activation function
   if(network.m_hlneurons[highlevelidx*m_hlm_nfieldwidth+2]>=0)
     {
      activationoffset=istart+network.m_hlneurons[highlevelidx*m_hlm_nfieldwidth+2]*m_nfieldwidth;
      network.m_structinfo.Set(activationoffset,fkind);
     }
   else
     {
      //--- check
      if(!CAp::Assert(fkind==0,__FUNCTION__+": you try to set activation function for neuron which can not have one"))
         return;
     }
//--- Threshold
   if(network.m_hlneurons[highlevelidx*m_hlm_nfieldwidth+3]>=0)
      network.m_weights.Set(network.m_hlneurons[highlevelidx*m_hlm_nfieldwidth+3],threshold);
   else
     {
      //--- check
      if(!CAp::Assert(threshold==0.0,__FUNCTION__+": you try to set non-zero threshold for neuron which can not have one"))
         return;
     }
  }
//+------------------------------------------------------------------+
//| This function modifies information about connection from I0-th   |
//| neuron of K0-th layer to I1-th neuron of K1-th layer.            |
//| INPUT PARAMETERS:                                                |
//|     Network     -   network                                      |
//|     K0          -   layer index                                  |
//|     I0          -   neuron index (within layer)                  |
//|     K1          -   layer index                                  |
//|     I1          -   neuron index (within layer)                  |
//|     W           -   connection weight (must be zero for          |
//|                     non-existent connections)                    |
//| This function:                                                   |
//| 1. throws exception if layer or neuron with given index do not   |
//|    exists.                                                       |
//| 2. throws exception if you try to set non-zero weight for        |
//|    non-existent connection                                       |
//+------------------------------------------------------------------+
void CMLPBase::MLPSetWeight(CMultilayerPerceptron &network,const int k0,
                            const int i0,const int k1,
                            const int i1,const double w)
  {
//--- create variables
   int ccnt=0;
   int highlevelidx=0;
//--- initialization
   ccnt=CAp::Len(network.m_hlconnections)/m_hlconm_nfieldwidth;
//--- check params
   if(!CAp::Assert(k0>=0 && k0<CAp::Len(network.m_hllayersizes),__FUNCTION__+": incorrect (nonexistent) K0"))
      return;
//--- check
   if(!CAp::Assert(i0>=0 && i0<network.m_hllayersizes[k0],__FUNCTION__+": incorrect (nonexistent) I0"))
      return;
//--- check
   if(!CAp::Assert(k1>=0 && k1<CAp::Len(network.m_hllayersizes),__FUNCTION__+": incorrect (nonexistent) K1"))
      return;
//--- check
   if(!CAp::Assert(i1>=0 && i1<network.m_hllayersizes[k1],__FUNCTION__+": incorrect (nonexistent) I1"))
      return;
//--- check
   if(!CAp::Assert(MathIsValidNumber(w),__FUNCTION__+": infinite or NAN weight"))
      return;
//--- search
   network.m_integerbuf.Set(0,k0);
   network.m_integerbuf.Set(1,i0);
   network.m_integerbuf.Set(2,k1);
   network.m_integerbuf.Set(3,i1);
//--- function call
   highlevelidx=CApServ::RecSearch(network.m_hlconnections,m_hlconm_nfieldwidth,4,0,ccnt,network.m_integerbuf);
//--- check
   if(highlevelidx>=0)
      network.m_weights.Set(network.m_hlconnections[highlevelidx*m_hlconm_nfieldwidth+4],w);
   else
     {
      //--- check
      if(!CAp::Assert(w==0.0,__FUNCTION__+": you try to set non-zero weight for non-existent connection"))
         return;
     }
  }
//+------------------------------------------------------------------+
//| Neural network activation function                               |
//| INPUT PARAMETERS:                                                |
//|     NET         -   neuron input                                 |
//|     K           -   function index (zero for linear function)    |
//| OUTPUT PARAMETERS:                                               |
//|     F           -   function                                     |
//|     DF          -   its derivative                               |
//|     D2F         -   its second derivative                        |
//+------------------------------------------------------------------+
void CMLPBase::MLPActivationFunction(double net,const int k,double &f,
                                     double &df,double &d2f)
  {
//--- create variables
   double net2=0;
   double arg=0;
   double root=0;
   double r=0;
//--- initialization
   f=0;
   df=0;
   d2f=0;
//--- check
   if(k==0 || k==-5)
     {
      f=net;
      df=1;
      d2f=0;
      //--- exit the function
      return;
     }
//--- check
   if(k==1)
     {
      //--- TanH activation function
      if(MathAbs(net)<100.0)
         f=MathTanh(net);
      else
         f=MathSign(net);
      //--- change values
      df=1-CMath::Sqr(f);
      d2f=-(2*f*df);
      //--- exit the function
      return;
     }
//--- check
   if(k==3)
     {
      //--- EX activation function
      if(net>=0.0)
        {
         //--- change values
         net2=net*net;
         arg=net2+1;
         root=MathSqrt(arg);
         f=net+root;
         r=net/root;
         df=1+r;
         d2f=(root-net*r)/arg;
        }
      else
        {
         //--- change values
         f=MathExp(net);
         df=f;
         d2f=f;
        }
      //--- exit the function
      return;
     }
//--- check
   if(k==2)
     {
      //--- calculation
      f=MathExp(-CMath::Sqr(net));
      df=-(2*net*f);
      d2f=-(2*(f+df*net));
      //--- exit the function
      return;
     }
  }
//+------------------------------------------------------------------+
//| Procesing                                                        |
//| INPUT PARAMETERS:                                                |
//|     Network -   neural network                                   |
//|     X       -   input vector,  array[0..NIn-1].                  |
//| OUTPUT PARAMETERS:                                               |
//|     Y       -   result. Regression estimate when solving         |
//|                 regression task, vector of posterior             |
//|                 probabilities for classification task.           |
//| See also MLPProcessI                                             |
//+------------------------------------------------------------------+
void CMLPBase::MLPProcess(CMultilayerPerceptron &network,double &x[],
                          double &y[])
  {
//--- check
   CRowDouble X=x;
   CRowDouble Y=vector<double>::Zeros(network.m_structinfo[2]);
//--- function call
   MLPInternalProcessVector(network.m_structinfo,network.m_weights,network.m_columnmeans,network.m_columnsigmas,network.m_neurons,network.m_dfdnet,X,Y);
//--- return result
   Y.ToArray(y);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CMLPBase::MLPProcess(CMultilayerPerceptron &network,CRowDouble &x,
                          CRowDouble &y)
  {
//--- check
   if(y.Size()<network.m_structinfo[2])
      y.Resize(network.m_structinfo[2]);
//--- function call
   MLPInternalProcessVector(network.m_structinfo,network.m_weights,network.m_columnmeans,network.m_columnsigmas,network.m_neurons,network.m_dfdnet,x,y);
  }
//+------------------------------------------------------------------+
//| 'interactive' variant of MLPProcess for languages like Python    |
//| which support constructs like "Y = MLPProcess(NN,X)" and         |
//| interactive mode of the interpreter                              |
//| This function allocates new array on each call, so it is         |
//| significantly slower than its 'non-interactive' counterpart,     |
//| but it is more convenient when you call it from command line.    |
//+------------------------------------------------------------------+
void CMLPBase::MLPProcessI(CMultilayerPerceptron &network,double &x[],
                           double &y[])
  {
   MLPProcess(network,x,y);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CMLPBase::MLPProcessI(CMultilayerPerceptron &network,CRowDouble &x,
                           CRowDouble &y)
  {
   MLPProcess(network,x,y);
  }
//+------------------------------------------------------------------+
//| Error function for neural network,internal subroutine.           |
//+------------------------------------------------------------------+
double CMLPBase::MLPError(CMultilayerPerceptron &network,
                          CMatrixDouble &xy,const int npoints)
  {
   double result=0;
//--- check
   if(!CAp::Assert(xy.Rows()>=npoints,__FUNCTION__": XY has less than NPoints rows"))
      return(result);
   if(npoints>0)
     {
      if(MLPIsSoftMax(network))
        {
         if(!CAp::Assert(xy.Cols()>MLPGetInputsCount(network),__FUNCTION__": XY has less than NIn+1 columns"))
            return(result);
        }
      else
         if(!CAp::Assert(xy.Cols()>=MLPGetInputsCount(network)+MLPGetOutputsCount(network),__FUNCTION__": XY has less than NIn+NOut columns"))
            return(result);
     }
//--- function call
   MLPAllErrorsX(network,xy,network.m_dummysxy,npoints,0,network.m_dummyidx,0,npoints,0,network.m_err);
   result=CMath::Sqr(network.m_err.m_RMSError)*npoints*MLPGetOutputsCount(network)/2;
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| Error of the neural network on dataset given by sparse matrix.   |
//| INPUT PARAMETERS:                                                |
//|   Network     -  neural network                                  |
//|   XY          -  training set, see below for information on the  |
//|                  training set format. This function checks       |
//|                  correctness of the dataset (no NANs/INFs, class |
//|                  numbers are correct) and throws exception when  |
//|                  incorrect dataset is passed. Sparse matrix must |
//|                  use CRS format for storage.                     |
//|   NPoints     -  points count, >=0                               |
//| RESULT:                                                          |
//|   sum-of-squares error, SUM(sqr(y[i]-desired_y[i])/2)            |
//| DATASET FORMAT:                                                  |
//| This function uses two different dataset formats - one for       |
//| regression networks, another one for classification networks.    |
//| For regression networks with NIn inputs and NOut outputs         |
//| following dataset format is used:                                |
//|   * dataset is given by NPoints*(NIn+NOut) matrix                |
//|   * each row corresponds to one example                          |
//|   * first NIn columns are inputs, next NOut columns are outputs  |
//| For classification networks with NIn inputs and NClasses clases  |
//| following dataset format is used:                                |
//|   * dataset is given by NPoints*(NIn+1) matrix                   |
//|   * each row corresponds to one example                          |
//|   * first NIn columns are inputs, last column stores class number|
//|     (from 0 to NClasses-1).                                      |
//+------------------------------------------------------------------+
double CMLPBase::MLPErrorSparse(CMultilayerPerceptron &network,
                                CSparseMatrix &xy,int npoints)
  {
   double result=0;
//--- check
   if(!CAp::Assert(CSparse::SparseIsCRS(xy),__FUNCTION__": XY is not in CRS format."))
      return(result);
   if(!CAp::Assert(CSparse::SparseGetNRows(xy)>=npoints,__FUNCTION__": XY has less than NPoints rows"))
      return(result);

   if(npoints>0)
     {
      if(MLPIsSoftMax(network))
        {
         if(!CAp::Assert(CSparse::SparseGetNCols(xy)>MLPGetInputsCount(network),__FUNCTION__": XY has less than NIn+1 columns"))
            return(result);
        }
      else
         if(!CAp::Assert(CSparse::SparseGetNCols(xy)>=MLPGetInputsCount(network)+MLPGetOutputsCount(network),__FUNCTION__": XY has less than NIn+NOut columns"))
            return(result);
     }
   MLPAllErrorsX(network,network.m_dummydxy,xy,npoints,1,network.m_dummyidx,0,npoints,0,network.m_err);
   result=CMath::Sqr(network.m_err.m_RMSError)*npoints*MLPGetOutputsCount(network)/2;
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| Natural error function for neural network,internal subroutine.   |
//+------------------------------------------------------------------+
double CMLPBase::MLPErrorN(CMultilayerPerceptron &network,
                           CMatrixDouble &xy,const int ssize)
  {
//--- create variables
   double result=0;
   int    i=0;
   int    k=0;
   int    nin=0;
   int    nout=0;
   int    wcount=0;
   double e=0;
   int    i_=0;
   int    i1_=0;
//--- function call
   MLPProperties(network,nin,nout,wcount);
   matrix<double> splitted[];
   ulong parts[]={nin,nout};
   xy.Split(parts,1,splitted);
//--- calculation
   for(i=0; i<ssize; i++)
     {
      //--- Process vector
      network.m_x=splitted[0].Row(i);
      //--- function call
      MLPProcess(network,network.m_x,network.m_y);
      //--- Update error function
      if(network.m_structinfo[6]==0)
        {
         //--- Least squares error function
         network.m_y-=splitted[1].Row(i);
         //--- calculation
         e=network.m_y.Dot(network.m_y);
         result+=e/2;
        }
      else
        {
         //--- Cross-entropy error function
         k=(int)MathRound(splitted[1][i,0]);
         //--- check
         if(k>=0 && k<nout)
            result+=SafeCrossEntropy(1,network.m_y[k]);
        }
     }
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| Classification error                                             |
//+------------------------------------------------------------------+
int CMLPBase::MLPClsError(CMultilayerPerceptron &network,
                          CMatrixDouble &xy,const int npoints)
  {
   int result=0;
//--- check
   if(!CAp::Assert(xy.Rows()>=npoints,__FUNCTION__": XY has less than NPoints rows"))
      return(result);
   if(npoints>0)
     {
      if(MLPIsSoftMax(network))
        {
         if(!CAp::Assert(xy.Cols()>MLPGetInputsCount(network),__FUNCTION__": XY has less than NIn+1 columns"))
            return(result);
        }
      else
         if(!CAp::Assert(xy.Cols()>=MLPGetInputsCount(network)+MLPGetOutputsCount(network),__FUNCTION__": XY has less than NIn+NOut columns"))
            return(result);
     }
//--- function call
   MLPAllErrorsX(network,xy,network.m_dummysxy,npoints,0,network.m_dummyidx,0,npoints,0,network.m_err);
   result=(int)MathRound(npoints*network.m_err.m_RelCLSError);
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| Relative classification error on the test set                    |
//| INPUT PARAMETERS:                                                |
//|     Network -   network                                          |
//|     XY      -   test set                                         |
//|     NPoints -   test set size                                    |
//| RESULT:                                                          |
//|     percent of incorrectly classified cases. Works both for      |
//|     classifier networks and general purpose networks used as     |
//|     classifiers.                                                 |
//+------------------------------------------------------------------+
double CMLPBase::MLPRelClsError(CMultilayerPerceptron &network,
                                CMatrixDouble &xy,const int npoints)
  {
   return((double)MLPClsError(network,xy,npoints)/(double)npoints);
  }
//+------------------------------------------------------------------+
//| Relative classification error on the test set given by sparse    |
//| matrix.                                                          |
//| INPUT PARAMETERS:                                                |
//|   Network     -  neural network;                                 |
//|   XY          -  training set, see below for information on the  |
//|                  training set format. Sparse matrix must use CRS |
//|                  format for storage.                             |
//|   NPoints     -  points count, >=0.                              |
//| RESULT:                                                          |
//|   Percent of incorrectly classified cases. Works both for        |
//|   classifier networks and general purpose networks used as       |
//|   classifiers.                                                   |
//| DATASET FORMAT:                                                  |
//|   This function uses two different dataset formats - one for     |
//|   regression networks, another one for classification networks.  |
//|   For regression networks with NIn inputs and NOut outputs       |
//|   following dataset format is used:                              |
//|      * dataset is given by NPoints*(NIn+NOut) matrix             |
//|      * each row corresponds to one example                       |
//|      * first NIn columns are inputs, next NOut columns are       |
//|        outputs                                                   |
//|   For classification networks with NIn inputs and NClasses clases|
//|   following dataset format is used:                              |
//|      * dataset is given by NPoints*(NIn+1) matrix                |
//|      * each row corresponds to one example                       |
//|      * first NIn columns are inputs, last column stores class    |
//|        number (from 0 to NClasses-1).                            |
//+------------------------------------------------------------------+
double CMLPBase::MLPRelClsErrorSparse(CMultilayerPerceptron &network,
                                      CSparseMatrix &xy,int npoints)
  {
   double result=0;
//--- check
   if(!CAp::Assert(CSparse::SparseIsCRS(xy),__FUNCTION__": sparse matrix XY is not in CRS format."))
      return(result);
   if(!CAp::Assert(CSparse::SparseGetNRows(xy)>=npoints,__FUNCTION__": sparse matrix XY has less than NPoints rows"))
      return(result);
   if(npoints>0)
     {
      if(MLPIsSoftMax(network))
        {
         if(!CAp::Assert(CSparse::SparseGetNCols(xy)>MLPGetInputsCount(network),__FUNCTION__": sparse matrix XY has less than NIn+1 columns"))
            return(result);
        }
      else
         if(!CAp::Assert(CSparse::SparseGetNCols(xy)>=MLPGetInputsCount(network)+MLPGetOutputsCount(network),__FUNCTION__": sparse matrix XY has less than NIn+NOut columns"))
            return(result);
     }
//--- function call
   MLPAllErrorsX(network,network.m_dummydxy,xy,npoints,1,network.m_dummyidx,0,npoints,0,network.m_err);
//--- return result
   return(network.m_err.m_RelCLSError);
  }
//+------------------------------------------------------------------+
//| Average cross-entropy (in bits per element) on the test set      |
//| INPUT PARAMETERS:                                                |
//|     Network -   neural network                                   |
//|     XY      -   test set                                         |
//|     NPoints -   test set size                                    |
//| RESULT:                                                          |
//|     CrossEntropy/(NPoints*LN(2)).                                |
//|     Zero if network solves regression task.                      |
//+------------------------------------------------------------------+
double CMLPBase::MLPAvgCE(CMultilayerPerceptron &network,CMatrixDouble &xy,
                          const int npoints)
  {
   double result=0;
//--- check
   if(!CAp::Assert(xy.Rows()>=npoints,__FUNCTION__": XY has less than NPoints rows"))
      return(result);
   if(npoints>0)
     {
      if(MLPIsSoftMax(network))
        {
         if(!CAp::Assert(xy.Cols()>MLPGetInputsCount(network),__FUNCTION__": XY has less than NIn+1 columns"))
            return(result);
        }
      else
         if(!CAp::Assert(xy.Cols()>=MLPGetInputsCount(network)+MLPGetOutputsCount(network),__FUNCTION__": XY has less than NIn+NOut columns"))
            return(result);
     }
//--- function call
   MLPAllErrorsX(network,xy,network.m_dummysxy,npoints,0,network.m_dummyidx,0,npoints,0,network.m_err);
//--- return result
   return(network.m_err.m_AvgCE);
  }
//+------------------------------------------------------------------+
//| Average cross-entropy (in bits per element) on the test set given|
//| by sparse matrix.                                                |
//| INPUT PARAMETERS:                                                |
//|   Network     -  neural network;                                 |
//|   XY          -  training set, see below for information on the  |
//|                  training set format. This function checks       |
//|                  correctness of the dataset (no NANs/INFs, class |
//|                  numbers are correct) and throws exception when  |
//|                  incorrect dataset is passed. Sparse matrix must |
//|                  use CRS format for storage.                     |
//|   NPoints     -  points count, >=0.                              |
//| RESULT:                                                          |
//|   CrossEntropy/(NPoints*LN(2)).                                  |
//|   Zero if network solves regression task.                        |
//| DATASET FORMAT:                                                  |
//|   This function uses two different dataset formats - one for     |
//|   regression networks, another one for classification networks.  |
//|   For regression networks with NIn inputs and NOut outputs       |
//|   following dataset format is used:                              |
//|      * dataset is given by NPoints*(NIn+NOut) matrix             |
//|      * each row corresponds to one example                       |
//|      * first NIn columns are inputs, next NOut columns are       |
//|        outputs                                                   |
//|   For classification networks with NIn inputs and NClasses clases|
//|   following dataset format is used:                              |
//|      * dataset is given by NPoints*(NIn+1) matrix                |
//|      * each row corresponds to one example                       |
//|      * first NIn columns are inputs, last column stores class    |
//|        number (from 0 to NClasses-1).                            |
//+------------------------------------------------------------------+
double CMLPBase::MLPAvgCESparse(CMultilayerPerceptron &network,
                                CSparseMatrix &xy,int npoints)
  {
   double result=0;
//--- check
   if(!CAp::Assert(CSparse::SparseIsCRS(xy),__FUNCTION__": sparse matrix XY is not in CRS format."))
      return(result);
   if(!CAp::Assert(CSparse::SparseGetNRows(xy)>=npoints,__FUNCTION__": sparse matrix XY has less than NPoints rows"))
      return(result);
   if(npoints>0)
     {
      if(MLPIsSoftMax(network))
        {
         if(!CAp::Assert(CSparse::SparseGetNCols(xy)>MLPGetInputsCount(network),__FUNCTION__": sparse matrix XY has less than NIn+1 columns"))
            return(result);
        }
      else
         if(!CAp::Assert(CSparse::SparseGetNCols(xy)>=MLPGetInputsCount(network)+MLPGetOutputsCount(network),__FUNCTION__": sparse matrix XY has less than NIn+NOut columns"))
            return(result);
     }
//--- function call
   MLPAllErrorsX(network,network.m_dummydxy,xy,npoints,1,network.m_dummyidx,0,npoints,0,network.m_err);
//--- return result
   return(network.m_err.m_AvgCE);
  }
//+------------------------------------------------------------------+
//| RMS error on the test set                                        |
//| INPUT PARAMETERS:                                                |
//|     Network -   neural network                                   |
//|     XY      -   test set                                         |
//|     NPoints -   test set size                                    |
//| RESULT:                                                          |
//|     root mean square error.                                      |
//|     Its meaning for regression task is obvious. As for           |
//|     classification task,RMS error means error when estimating    |
//|     posterior probabilities.                                     |
//+------------------------------------------------------------------+
double CMLPBase::MLPRMSError(CMultilayerPerceptron &network,
                             CMatrixDouble &xy,const int npoints)
  {
//--- check
   if(!CAp::Assert(xy.Rows()>=npoints,__FUNCTION__": XY has less than NPoints rows"))
      return(0);
   if(npoints>0)
     {
      if(MLPIsSoftMax(network))
        {
         if(!CAp::Assert(xy.Cols()>MLPGetInputsCount(network),__FUNCTION__": XY has less than NIn+1 columns"))
            return(0);
        }
      else
         if(!CAp::Assert(xy.Cols()>=MLPGetInputsCount(network)+MLPGetOutputsCount(network),__FUNCTION__": XY has less than NIn+NOut columns"))
            return(0);
     }
//--- function call
   MLPAllErrorsX(network,xy,network.m_dummysxy,npoints,0,network.m_dummyidx,0,npoints,0,network.m_err);
//--- return result
   return(network.m_err.m_RMSError);
  }
//+------------------------------------------------------------------+
//| RMS error on the test set given by sparse matrix.                |
//| INPUT PARAMETERS:                                                |
//|   Network     -  neural network;                                 |
//|   XY          -  training set, see below for information on the  |
//|                  training set format. This function checks       |
//|                  correctness of the dataset (no NANs/INFs, class |
//|                  numbers are correct) and throws exception when  |
//|                  incorrect dataset is passed. Sparse matrix must |
//|                  use CRS format for storage.                     |
//|   NPoints     -  points count, >=0.                              |
//| RESULT:                                                          |
//|   Root mean square error. Its meaning for regression task is     |
//|   obvious. As for classification task, RMS error means error when|
//|   estimating posterior probabilities.                            |
//| DATASET FORMAT:                                                  |
//|   This function uses two different dataset formats - one for     |
//|   regression networks, another one for classification networks.  |
//|   For regression networks with NIn inputs and NOut outputs       |
//|   following dataset format is used:                              |
//|      * dataset is given by NPoints*(NIn + NOut) matrix           |
//|      * each row corresponds to one example                       |
//|      * first NIn columns are inputs, next NOut columns are       |
//|        outputs                                                   |
//|   For classification networks with NIn inputs and NClasses       |
//|   clases following dataset format is used:                       |
//|      * dataset is given by NPoints*(NIn + 1) matrix              |
//|      * each row corresponds to one example                       |
//|      * first NIn columns are inputs, last column stores class    |
//|        number(from 0 to NClasses - 1).                           |
//+------------------------------------------------------------------+
double CMLPBase::MLPRMSErrorSparse(CMultilayerPerceptron &network,
                                   CSparseMatrix &xy,int npoints)
  {
//--- check
   if(!CAp::Assert(CSparse::SparseIsCRS(xy),__FUNCTION__": sparse matrix XY is not in CRS format."))
      return(0);
   if(!CAp::Assert(CSparse::SparseGetNRows(xy)>=npoints,__FUNCTION__": sparse matrix XY has less than NPoints rows"))
      return(0);

   if(npoints>0)
     {
      if(MLPIsSoftMax(network))
        {
         if(!CAp::Assert(CSparse::SparseGetNCols(xy)>MLPGetInputsCount(network),__FUNCTION__": sparse matrix XY has less than NIn+1 columns"))
            return(0);
        }
      else
         if(!CAp::Assert(CSparse::SparseGetNCols(xy)>=MLPGetInputsCount(network)+MLPGetOutputsCount(network),__FUNCTION__": sparse matrix XY has less than NIn+NOut columns"))
            return(0);
     }
//---function call
   MLPAllErrorsX(network,network.m_dummydxy,xy,npoints,1,network.m_dummyidx,0,npoints,0,network.m_err);
//--- return result
   return(network.m_err.m_RMSError);
  }
//+------------------------------------------------------------------+
//| Average error on the test set                                    |
//| INPUT PARAMETERS:                                                |
//|     Network -   neural network                                   |
//|     XY      -   test set                                         |
//|     NPoints -   test set size                                    |
//| RESULT:                                                          |
//|     Its meaning for regression task is obvious. As for           |
//|     classification task,it means average error when estimating   |
//|     posterior probabilities.                                     |
//+------------------------------------------------------------------+
double CMLPBase::MLPAvgError(CMultilayerPerceptron &network,
                             CMatrixDouble &xy,const int npoints)
  {
//--- check
   if(!CAp::Assert(xy.Rows()>=npoints,__FUNCTION__": XY has less than NPoints rows"))
      return(0);

   if(npoints>0)
     {
      if(MLPIsSoftMax(network))
        {
         if(!CAp::Assert(xy.Cols()>MLPGetInputsCount(network),__FUNCTION__": XY has less than NIn+1 columns"))
            return(0);
        }
      else
         if(!CAp::Assert(xy.Cols()>=MLPGetInputsCount(network)+MLPGetOutputsCount(network),__FUNCTION__": XY has less than NIn+NOut columns"))
            return(0);
     }
//--- function call
   MLPAllErrorsX(network,xy,network.m_dummysxy,npoints,0,network.m_dummyidx,0,npoints,0,network.m_err);
//--- return result
   return(network.m_err.m_AvgError);
  }
//+------------------------------------------------------------------+
//| Average absolute error on the test set given by sparse matrix.   |
//| INPUT PARAMETERS:                                                |
//|   Network     -  neural network;                                 |
//|   XY          -  training set, see below for information on the  |
//|                  training set format. This function checks       |
//|                  correctness of the dataset (no NANs/INFs, class |
//|                  numbers are correct) and throws exception when  |
//|                  incorrect dataset is passed. Sparse matrix must |
//|                  use CRS format for storage.                     |
//|   NPoints     -  points count, >=0.                              |
//| RESULT:                                                          |
//|   Its meaning for regression task is obvious. As for             |
//|   classification task, it means average error when estimating    |
//|   posterior probabilities.                                       |
//| DATASET FORMAT:                                                  |
//|   This function uses two different dataset formats - one for     |
//|   regression networks, another one for classification networks.  |
//|   For regression networks with NIn inputs and NOut outputs       |
//|   following dataset format is used:                              |
//|      * dataset is given by NPoints*(NIn+NOut) matrix             |
//|      * each row corresponds to one example                       |
//|      * first NIn columns are inputs, next NOut columns are       |
//|        outputs                                                   |
//|   For classification networks with NIn inputs and NClasses clases|
//|   following dataset format is used:                              |
//|      * dataset is given by NPoints*(NIn+1) matrix                |
//|      * each row corresponds to one example                       |
//|      * first NIn columns are inputs, last column stores class    |
//|        number (from 0 to NClasses-1).                            |
//+------------------------------------------------------------------+
double CMLPBase::MLPAvgErrorSparse(CMultilayerPerceptron &network,
                                   CSparseMatrix &xy,int npoints)
  {
//--- check
   if(!CAp::Assert(CSparse::SparseIsCRS(xy),__FUNCTION__": sparse matrix XY is not in CRS format."))
      return(0);
   if(!CAp::Assert(CSparse::SparseGetNRows(xy)>=npoints,__FUNCTION__": sparse matrix XY has less than NPoints rows"))
      return(0);

   if(npoints>0)
     {
      if(MLPIsSoftMax(network))
        {
         if(!CAp::Assert(CSparse::SparseGetNCols(xy)>MLPGetInputsCount(network),__FUNCTION__": sparse matrix XY has less than NIn+1 columns"))
            return(0);
        }
      else
         if(!CAp::Assert(CSparse::SparseGetNCols(xy)>=MLPGetInputsCount(network)+MLPGetOutputsCount(network),__FUNCTION__": sparse matrix XY has less than NIn+NOut columns"))
            return(0);
     }
//---function call
   MLPAllErrorsX(network,network.m_dummydxy,xy,npoints,1,network.m_dummyidx,0,npoints,0,network.m_err);
//--- return result
   return(network.m_err.m_AvgError);
  }
//+------------------------------------------------------------------+
//| Average relative error on the test set                           |
//| INPUT PARAMETERS:                                                |
//|     Network -   neural network                                   |
//|     XY      -   test set                                         |
//|     NPoints -   test set size                                    |
//| RESULT:                                                          |
//|     Its meaning for regression task is obvious. As for           |
//|     classification task, it means average relative error when    |
//|     estimating posterior probability of belonging to the correct |
//|     class.                                                       |
//+------------------------------------------------------------------+
double CMLPBase::MLPAvgRelError(CMultilayerPerceptron &network,
                                CMatrixDouble &xy,const int npoints)
  {
//--- check
   if(!CAp::Assert(xy.Rows()>=npoints,__FUNCTION__": XY has less than NPoints rows"))
      return(0);

   if(npoints>0)
     {
      if(MLPIsSoftMax(network))
        {
         if(!CAp::Assert(xy.Cols()>MLPGetInputsCount(network),__FUNCTION__": XY has less than NIn+1 columns"))
            return(0);
        }
      else
         if(!CAp::Assert(xy.Cols()>=MLPGetInputsCount(network)+MLPGetOutputsCount(network),__FUNCTION__": XY has less than NIn+NOut columns"))
            return(0);
     }
//--- function call
   MLPAllErrorsX(network,xy,network.m_dummysxy,npoints,0,network.m_dummyidx,0,npoints,0,network.m_err);
//--- return result
   return(network.m_err.m_AvgRelError);
  }
//+------------------------------------------------------------------+
//| Average relative error on the test set given by sparse matrix.   |
//| INPUT PARAMETERS:                                                |
//|   Network     -  neural network;                                 |
//|   XY          -  training set, see below for information on the  |
//|                  training set format. This function checks       |
//|                  correctness of the dataset (no NANs/INFs, class |
//|                  numbers are correct) and throws exception when  |
//|                  incorrect dataset is passed. Sparse matrix must |
//|                  use CRS format for storage.                     |
//|   NPoints     -  points count, >=0.                              |
//| RESULT:                                                          |
//|   Its meaning for regression task is obvious. As for             |
//|   classification task, it means average relative error when      |
//|   estimating posterior probability of belonging to the correct   |
//|   class.                                                         |
//| DATASET FORMAT:                                                  |
//|   This function uses two different dataset formats - one for     |
//|   regression networks, another one for classification networks.  |
//|   For regression networks with NIn inputs and NOut outputs       |
//|   following dataset format is used:                              |
//|      * dataset is given by NPoints*(NIn+NOut) matrix             |
//|      * each row corresponds to one example                       |
//|      * first NIn columns are inputs, next NOut columns are       |
//|        outputs                                                   |
//|   For classification networks with NIn inputs and NClasses clases|
//|   following dataset format is used:                              |
//|      * dataset is given by NPoints*(NIn+1) matrix                |
//|      * each row corresponds to one example                       |
//|      * first NIn columns are inputs, last column stores class    |
//|        number (from 0 to NClasses-1).                            |
//+------------------------------------------------------------------+
double CMLPBase::MLPAvgRelErrorSparse(CMultilayerPerceptron &network,
                                      CSparseMatrix &xy,int npoints)
  {
//--- check
   if(!CAp::Assert(CSparse::SparseIsCRS(xy),__FUNCTION__": sparse matrix XY is not in CRS format."))
      return(0);
   if(!CAp::Assert(CSparse::SparseGetNRows(xy)>=npoints,__FUNCTION__": sparse matrix XY has less than NPoints rows"))
      return(0);

   if(npoints>0)
     {
      if(MLPIsSoftMax(network))
        {
         if(!CAp::Assert(CSparse::SparseGetNCols(xy)>MLPGetInputsCount(network),__FUNCTION__": sparse matrix XY has less than NIn+1 columns"))
            return(0);
        }
      else
         if(!CAp::Assert(CSparse::SparseGetNCols(xy)>=MLPGetInputsCount(network)+MLPGetOutputsCount(network),__FUNCTION__": sparse matrix XY has less than NIn+NOut columns"))
            return(0);
     }
//---function call
   MLPAllErrorsX(network,network.m_dummydxy,xy,npoints,1,network.m_dummyidx,0,npoints,0,network.m_err);
//--- return result
   return(network.m_err.m_AvgRelError);
  }
//+------------------------------------------------------------------+
//| Gradient calculation                                             |
//| INPUT PARAMETERS:                                                |
//|     Network -   network initialized with one of the network      |
//|                 creation funcs                                   |
//|     X       -   input vector, length of array must be at least   |
//|                 NIn                                              |
//|     DesiredY-   desired outputs, length of array must be at least|
//|                 NOut                                             |
//|     Grad    -   possibly preallocated array. If size of array is |
//|                 smaller than WCount, it will be reallocated. It  |
//|                 is recommended to reuse previously allocated     |
//|                 array to reduce allocation overhead.             |
//| OUTPUT PARAMETERS:                                               |
//|     E       -   error function, SUM(sqr(y[i]-desiredy[i])/2,i)   |
//|     Grad    -   gradient of E with respect to weights of network,|
//|                 array[WCount]                                    |
//+------------------------------------------------------------------+
void CMLPBase::MLPGrad(CMultilayerPerceptron &network,double &x[],
                       double &desiredy[],double &e,double &grad[])
  {
   CRowDouble X=x;
   CRowDouble Y=desiredy;
   CRowDouble Grad;
   MLPGrad(network,X,Y,e,Grad);
   Grad.ToArray(grad);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CMLPBase::MLPGrad(CMultilayerPerceptron &network,CRowDouble &x,
                       CRowDouble &desiredy,double &e,CRowDouble &grad)
  {
//--- create variables
   int nout=0;
   int ntotal=0;
//--- Alloc
   if(CAp::Len(grad)<network.m_structinfo[4])
      grad.Resize(network.m_structinfo[4]);
//--- Prepare dError/dOut,internal structures
   MLPProcess(network,x,network.m_y);
//--- initialization
   nout=network.m_structinfo[2];
   ntotal=network.m_structinfo[3];
   e=MathPow(network.m_y.ToVector()-desiredy.ToVector(),2.0).Sum()/2.0;
//--- change values
   network.m_derror=vector<double>::Zeros(ntotal);
   for(int i=0; i<nout; i++)
      network.m_derror.Set(ntotal-nout+i,network.m_y[i]-desiredy[i]);
//--- gradient
   MLPInternalCalculateGradient(network,network.m_neurons,network.m_weights,network.m_derror,grad,false);
  }
//+------------------------------------------------------------------+
//| Gradient calculation (natural error function is used)            |
//| INPUT PARAMETERS:                                                |
//|     Network -   network initialized with one of the network      |
//|                 creation funcs                                   |
//|     X       -   input vector, length of array must be at least   |
//|                 NIn                                              |
//|     DesiredY-   desired outputs, length of array must be at least|
//|                 NOut                                             |
//|     Grad    -   possibly preallocated array. If size of array is |
//|                 smaller than WCount, it will be reallocated. It  |
//|                 is recommended to reuse previously allocated     |
//|                 array to reduce allocation overhead.             |
//| OUTPUT PARAMETERS:                                               |
//|     E       -   error function, sum-of-squares for regression    |
//|                 networks, cross-entropy for classification       |
//|                 networks.                                        |
//|     Grad    -   gradient of E with respect to weights of network,|
//|                 array[WCount]                                    |
//+------------------------------------------------------------------+
void CMLPBase::MLPGradN(CMultilayerPerceptron &network,double &x[],
                        double &desiredy[],double &e,double &grad[])
  {
   CRowDouble X=x;
   CRowDouble Y=desiredy;
   CRowDouble Grad;
   MLPGradN(network,X,Y,e,Grad);
   Grad.ToArray(grad);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CMLPBase::MLPGradN(CMultilayerPerceptron &network,CRowDouble &x,
                        CRowDouble &desiredy,double &e,CRowDouble &grad)
  {
//--- create variables
   double s=0;
   int    nout=0;
   int    ntotal=0;
//--- initialization
   e=0;
//--- Alloc
   if(grad.Size()<network.m_structinfo[4])
      grad.Resize(network.m_structinfo[4]);
//--- Prepare dError/dOut,internal structures
   MLPProcess(network,x,network.m_y);
//--- change values
   nout=network.m_structinfo[2];
   ntotal=network.m_structinfo[3];
   network.m_derror=vector<double>::Zeros(ntotal);
   e=0;
//--- check
   if(network.m_structinfo[6]==0)
     {
      //--- Regression network,least squares
      e=MathPow(network.m_y.ToVector()-desiredy.ToVector(),2.0).Sum()/2.0;
      for(int i=0; i<=nout-1; i++)
         network.m_derror.Set(ntotal-nout+i,network.m_y[i]-desiredy[i]);
     }
   else
     {
      //--- Classification network,cross-entropy
      s=desiredy.Sum();
      for(int i=0; i<nout; i++)
        {
         network.m_derror.Set(ntotal-nout+i,s*network.m_y[i]-desiredy[i]);
         e+=SafeCrossEntropy(desiredy[i],network.m_y[i]);
        }
     }
//--- gradient
   MLPInternalCalculateGradient(network,network.m_neurons,network.m_weights,network.m_derror,grad,true);
  }
//+------------------------------------------------------------------+
//| Batch gradient calculation for a set of inputs/outputs           |
//| INPUT PARAMETERS:                                                |
//|     Network -   network initialized with one of the network      |
//|                 creation funcs                                   |
//|     XY      -   set of inputs/outputs; one sample = one row;     |
//|                 first NIn columns contain inputs,                |
//|                 next NOut columns - desired outputs.             |
//|     SSize   -   number of elements in XY                         |
//|     Grad    -   possibly preallocated array. If size of array is |
//|                 smaller than WCount, it will be reallocated. It  |
//|                 is recommended to reuse previously allocated     |
//|                 array to reduce allocation overhead.             |
//| OUTPUT PARAMETERS:                                               |
//|     E       -   error function, SUM(sqr(y[i]-desiredy[i])/2,i)   |
//|     Grad    -   gradient of E with respect to weights of network,|
//|                 array[WCount]                                    |
//+------------------------------------------------------------------+
void CMLPBase::MLPGradBatch(CMultilayerPerceptron &network,
                            CMatrixDouble &xy,const int ssize,
                            double &e,double &grad[])
  {
   CRowDouble Grad;
   MLPGradBatch(network,xy,ssize,e,Grad);
   Grad.ToArray(grad);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CMLPBase::MLPGradBatch(CMultilayerPerceptron &network,
                            CMatrixDouble &xy,const int ssize,
                            double &e,CRowDouble &grad)
  {
//--- create variables
   int nin=0;
   int nout=0;
   int wcount=0;
//--- function call
   MLPProperties(network,nin,nout,wcount);
//--- initialization
   grad=vector<double>::Zeros(wcount);
   e=0;
//--- function call
   MLPGradBatchX(network,xy,network.m_dummysxy,ssize,0,network.m_dummyidx,0,ssize,0,e,grad);
  }
//+------------------------------------------------------------------+
//| Batch gradient calculation for a set of inputs/outputs given by  |
//| sparse matrices                                                  |
//| INPUT PARAMETERS:                                                |
//|   Network  -  network initialized with one of the network        |
//|               creation funcs                                     |
//|   XY       -  original dataset in sparse format; one sample = one|
//|               row:                                               |
//|               * MATRIX MUST BE STORED IN CRS FORMAT              |
//|               * first NIn columns contain inputs.                |
//|               * for regression problem, next NOut columns store  |
//|                 desired outputs.                                 |
//|               * for classification problem, next column (just    |
//|                 one!) stores class number.                       |
//|   SSize    -  number of elements in XY                           |
//|   Grad     -  possibly preallocated array. If size of array is   |
//|               smaller than WCount, it will be reallocated. It is |
//|               recommended to reuse previously allocated array to |
//|               reduce allocation overhead.                        |
//| OUTPUT PARAMETERS:                                               |
//|   E        -  error function, SUM(sqr(y[i]-desiredy[i])/2,i)     |
//|   Grad     -  gradient of E with respect to weights of network,  |
//|               array[WCount]                                      |
//+------------------------------------------------------------------+
void CMLPBase::MLPGradBatchSparse(CMultilayerPerceptron &network,
                                  CSparseMatrix &xy,int ssize,
                                  double &e,CRowDouble &grad)
  {
//--- create variables
   int nin=0;
   int nout=0;
   int wcount=0;
   int subset0=0;
   int subset1=ssize;
   int subsettype=0;
   e=0;
//--- check
   if(!CAp::Assert(ssize>=0,__FUNCTION__": SSize<0"))
      return;
   if(!CAp::Assert(CSparse::SparseIsCRS(xy),__FUNCTION__": sparse matrix XY must be in CRS format."))
      return;

   MLPProperties(network,nin,nout,wcount);
   grad=vector<double>::Zeros(wcount);
   MLPGradBatchX(network,network.m_dummydxy,xy,ssize,1,network.m_dummyidx,subset0,subset1,subsettype,e,grad);
  }
//+------------------------------------------------------------------+
//| Batch gradient calculation for a subset of dataset               |
//| INPUT PARAMETERS:                                                |
//|   Network  -  network initialized with one of the network        |
//|               creation funcs                                     |
//|   XY       -  original dataset in dense format; one sample = one |
//|               row:                                               |
//|               * first NIn columns contain inputs,                |
//|               * for regression problem, next NOut columns store  |
//|                 desired outputs.                                 |
//|               * for classification problem, next column (just    |
//|                 one!) stores class number.                       |
//|   SetSize  -  real size of XY, SetSize>=0;                       |
//|   Idx      -  subset of SubsetSize elements, array[SubsetSize]:  |
//|               * Idx[I] stores row index in the original dataset  |
//|                 which is given by XY. Gradient is calculated with|
//|                 respect to rows whose indexes are stored in Idx[]|
//|               * Idx[]  must store correct indexes; this function |
//|                 throws an exception in case incorrect index (less|
//|                 than 0 or larger than rows(XY)) is given         |
//|               * Idx[] may store indexes in any order and even    |
//|                 with repetitions.                                |
//|   SubsetSize- number of elements in Idx[] array:                 |
//|               * positive value means that subset given by Idx[]  |
//|                 is processed                                     |
//|               * zero value results in zero gradient              |
//|               * negative value means that full dataset is        |
//|                 processed                                        |
//|   Grad     -  possibly preallocated array. If size of array is   |
//|               smaller than WCount, it will be reallocated. It is |
//|               recommended to reuse previously allocated array to |
//|               reduce allocation overhead.                        |
//| OUTPUT PARAMETERS:                                               |
//|   E        -  error function, SUM(sqr(y[i]-desiredy[i])/2,i)     |
//|   Grad     -  gradient of E with respect to weights of network,  |
//|               array[WCount]                                      |
//+------------------------------------------------------------------+
void CMLPBase::MLPGradBatchSubset(CMultilayerPerceptron &network,
                                  CMatrixDouble &xy,int setsize,
                                  CRowInt &idx,int subsetsize,
                                  double &e,CRowDouble &grad)
  {
//--- create variables
   int nin=0;
   int nout=0;
   int wcount=0;
   int npoints=0;
   int subset0=0;
   int subset1=0;
   int subsettype=0;
   e=0;
//--- check
   if(!CAp::Assert(setsize>=0,__FUNCTION__": SetSize<0"))
      return;
   if(!CAp::Assert(subsetsize<=idx.Size(),__FUNCTION__": SubsetSize>Length(Idx)"))
      return;

   npoints=setsize;
   if(subsetsize<0)
     {
      subset0=0;
      subset1=setsize;
      subsettype=0;
     }
   else
     {
      subset0=0;
      subset1=subsetsize;
      subsettype=1;
      for(int i=0; i<subsetsize; i++)
        {
         if(!CAp::Assert(idx[i]>=0,__FUNCTION__": incorrect index of XY row(Idx[I]<0)"))
            return;
         if(!CAp::Assert(idx[i]<npoints,__FUNCTION__": incorrect index of XY row(Idx[I]>Rows(XY)-1)"))
            return;
        }
     }
   MLPProperties(network,nin,nout,wcount);
   grad=vector<double>::Zeros(wcount);
   MLPGradBatchX(network,xy,network.m_dummysxy,setsize,0,idx,subset0,subset1,subsettype,e,grad);
  }
//+------------------------------------------------------------------+
//| Batch gradient calculation for a set of inputs/outputs for a     |
//| subset of dataset given by set of indexes.                       |
//| INPUT PARAMETERS:                                                |
//|   Network  -  network initialized with one of the network        |
//|               creation funcs                                     |
//|   XY       -  original dataset in sparse format; one sample = one|
//|               row:                                               |
//|               * MATRIX MUST BE STORED IN CRS FORMAT              |
//|               * first NIn columns contain inputs,                |
//|               * for regression problem, next NOut columns store  |
//|                 desired outputs.                                 |
//|               * for classification problem, next column (just    |
//|                 one!) stores class number.                       |
//|   SetSize  -  real size of XY, SetSize>=0;                       |
//|   Idx      -  subset of SubsetSize elements, array[SubsetSize]:  |
//|               * Idx[I] stores row index in the original dataset  |
//|                 which is given by XY. Gradient is calculated with|
//|                 respect to rows whose indexes are stored in Idx[]|
//|               * Idx[] must store correct indexes; this function  |
//|                 throws an exception in case incorrect index (less|
//|                 than 0 or larger than rows(XY)) is given         |
//|               * Idx[] may store indexes in any order and even    |
//|                 with repetitions.                                |
//|   SubsetSize- number of elements in Idx[] array:                 |
//|               * positive value means that subset given by Idx[]  |
//|                 is processed                                     |
//|               * zero value results in zero gradient              |
//|               * negative value means that full dataset is        |
//|                 processed                                        |
//|   Grad     -  possibly preallocated array. If size of array is   |
//|               smaller than WCount, it will be reallocated. It is |
//|               recommended to reuse previously allocated array to |
//|               reduce allocation overhead.                        |
//| OUTPUT PARAMETERS:                                               |
//|   E        -  error function, SUM(sqr(y[i]-desiredy[i])/2,i)     |
//|   Grad     -  gradient of E with respect to weights of network,  |
//|               array[WCount]                                      |
//| NOTE: when SubsetSize<0 is used full dataset by call             |
//|       MLPGradBatchSparse function.                               |
//+------------------------------------------------------------------+
void CMLPBase::MLPGradBatchSparseSubset(CMultilayerPerceptron &network,
                                        CSparseMatrix &xy,int setsize,
                                        CRowInt &idx,int subsetsize,
                                        double &e,CRowDouble &grad)
  {
//--- create variables
   int nin=0;
   int nout=0;
   int wcount=0;
   int npoints=0;
   int subset0=0;
   int subset1=0;
   int subsettype=0;
   e=0;
//--- check
   if(!CAp::Assert(setsize>=0,__FUNCTION__": SetSize<0"))
      return;
   if(!CAp::Assert(subsetsize<=idx.Size(),__FUNCTION__": SubsetSize>Length(Idx)"))
      return;
   if(!CAp::Assert(CSparse::SparseIsCRS(xy),__FUNCTION__": sparse matrix XY must be in CRS format."))
      return;

   npoints=setsize;
   if(subsetsize<0)
     {
      subset0=0;
      subset1=setsize;
      subsettype=0;
     }
   else
     {
      subset0=0;
      subset1=subsetsize;
      subsettype=1;
      for(int i=0; i<subsetsize; i++)
        {
         if(!CAp::Assert(idx[i]>=0,__FUNCTION__": incorrect index of XY row(Idx[I]<0)"))
            return;
         if(!CAp::Assert(idx[i]<npoints,__FUNCTION__": incorrect index of XY row(Idx[I]>Rows(XY)-1)"))
            return;
        }
     }
   MLPProperties(network,nin,nout,wcount);
   grad=vector<double>::Zeros(wcount);
   MLPGradBatchX(network,network.m_dummydxy,xy,setsize,1,idx,subset0,subset1,subsettype,e,grad);
  }
//+------------------------------------------------------------------+
//| Internal function which actually calculates batch gradient for a |
//| subset or full dataset, which can be represented in different    |
//| formats.                                                         |
//| THIS FUNCTION IS NOT INTENDED TO BE USED BY ALGLIB USERS!        |
//+------------------------------------------------------------------+
void CMLPBase::MLPGradBatchX(CMultilayerPerceptron &network,
                             CMatrixDouble &densexy,
                             CSparseMatrix &sparsexy,
                             int datasetsize,
                             int datasettype,
                             CRowInt &idx,
                             int subset0,
                             int subset1,
                             int subsettype,
                             double &e,
                             CRowDouble &grad)
  {
//--- create variables
   int    nin=0;
   int    nout=0;
   int    wcount=0;
   int    rowsize=0;
   int    srcidx=0;
   int    cstart=0;
   int    csize=0;
   double problemcost=0;
   CMLPBuffers buf2;
   int    len0=0;
   int    len1=0;
   CMLPBuffers pbuf;
   CSMLPGrad sgrad;
//--- check
   if(!CAp::Assert(datasetsize>=0,__FUNCTION__": SetSize<0"))
      return;
   if(!CAp::Assert(datasettype==0 || datasettype==1,__FUNCTION__": DatasetType is incorrect"))
      return;
   if(!CAp::Assert(subsettype==0 || subsettype==1,__FUNCTION__": SubsetType is incorrect"))
      return;
//--- Determine network and dataset properties
   MLPProperties(network,nin,nout,wcount);
   if(MLPIsSoftMax(network))
      rowsize=nin+1;
   else
      rowsize=nin+nout;
//--- Chunked processing
   CHPCCores::HPCPrepareChunkedGradient(network.m_weights,wcount,MLPNTotal(network),nin,nout,pbuf);
   cstart=subset0;
   while(cstart<subset1)
     {
      //--- Determine size of current chunk and copy it to PBuf.XY
      csize=MathMin(subset1,cstart+pbuf.m_ChunkSize)-cstart;
      for(int j=0; j<csize; j++)
        {
         srcidx=-1;
         if(subsettype==0)
            srcidx=cstart+j;
         if(subsettype==1)
            srcidx=idx[cstart+j];
         if(!CAp::Assert(srcidx>=0,__FUNCTION__": internal error"))
            return;
         if(datasettype==0)
            pbuf.m_XY.Row(j,densexy,srcidx);
         if(datasettype==1)
           {
            CSparse::SparseGetRow(sparsexy,srcidx,pbuf.m_XYRow);
            pbuf.m_XY.Row(j,pbuf.m_XYRow);
           }
        }
      pbuf.m_XY.Resize(csize,rowsize);
      //--- Process chunk and advance line pointer
      MLPChunkedGradient(network,pbuf.m_XY,0,csize,pbuf.m_Batch4Buf,pbuf.m_HPCBuf,e,false);
      cstart=cstart+pbuf.m_ChunkSize;
     }
   CHPCCores::HPCFinalizeChunkedGradient(pbuf,grad);
  }
//+------------------------------------------------------------------+
//| Batch gradient calculation for a set of inputs/outputs           |
//| (natural error function is used)                                 |
//| INPUT PARAMETERS:                                                |
//|     Network -   network initialized with one of the network      |
//|                 creation funcs                                   |
//|     XY      -   set of inputs/outputs; one sample=one row;       |
//|                 first NIn columns contain inputs,                |
//|                 next NOut columns - desired outputs.             |
//|     SSize   -   number of elements in XY                         |
//|     Grad    -   possibly preallocated array. If size of array is |
//|                 smaller than WCount, it will be reallocated. It  |
//|                 is recommended to reuse previously allocated     |
//|                 array to reduce allocation overhead.             |
//| OUTPUT PARAMETERS:                                               |
//|     E       -   error function, sum-of-squares for regression    |
//|                 networks, cross-entropy for classification       |
//|                 networks.                                        |
//|     Grad    -   gradient of E with respect to weights of network,|
//|                 array[WCount]                                    |
//+------------------------------------------------------------------+
void CMLPBase::MLPGradNBatch(CMultilayerPerceptron &network,
                             CMatrixDouble &xy,const int ssize,
                             double &e,double &grad[])
  {
   CRowDouble Grad;
   MLPGradNBatch(network,xy,ssize,e,Grad);
   Grad.ToArray(grad);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CMLPBase::MLPGradNBatch(CMultilayerPerceptron &network,
                             CMatrixDouble &xy,const int ssize,
                             double &e,CRowDouble &grad)
  {
//--- create variables
   int i=0;
   int nin=0;
   int nout=0;
   int wcount=0;
   CMLPBuffers pbuf;
//--- function call
   MLPProperties(network,nin,nout,wcount);
   CHPCCores::HPCPrepareChunkedGradient(network.m_weights,wcount,MLPNTotal(network),nin,nout,pbuf);
//--- initialization
   i=0;
   e=0;
   grad=vector<double>::Zeros(wcount);
//--- calculation
   while(i<ssize)
     {
      MLPChunkedGradient(network,xy,i,MathMin(ssize,i+pbuf.m_ChunkSize)-i,pbuf.m_Batch4Buf,pbuf.m_HPCBuf,e,true);
      i+=pbuf.m_ChunkSize;
     }
   CHPCCores::HPCFinalizeChunkedGradient(pbuf,grad);
  }
//+------------------------------------------------------------------+
//| Batch Hessian calculation (natural error function) using         |
//| R-algorithm. Internal subroutine.                                |
//|      Hessian calculation based on R-algorithm described in       |
//|      "Fast Exact Multiplication by the Hessian",                 |
//|      B. A. Pearlmutter,                                          |
//|      Neural Computation, 1994.                                   |
//+------------------------------------------------------------------+
void CMLPBase::MLPHessianNBatch(CMultilayerPerceptron &network,
                                CMatrixDouble &xy,const int ssize,
                                double &e,double &grad[],
                                CMatrixDouble &h)
  {
//---
   CRowDouble Grad;
//--- initialization
   e=0;
//--- function call
   MLPHessianBatchInternal(network,xy,ssize,true,e,Grad,h);
//--- return result
   Grad.ToArray(grad);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CMLPBase::MLPHessianNBatch(CMultilayerPerceptron &network,
                                CMatrixDouble &xy,const int ssize,
                                double &e,CRowDouble &grad,
                                CMatrixDouble &h)
  {
//--- initialization
   e=0;
//--- function call
   MLPHessianBatchInternal(network,xy,ssize,true,e,grad,h);
  }
//+------------------------------------------------------------------+
//| Batch Hessian calculation using R-algorithm.                     |
//| Internal subroutine.                                             |
//|      Hessian calculation based on R-algorithm described in       |
//|      "Fast Exact Multiplication by the Hessian",                 |
//|      B. A. Pearlmutter,                                          |
//|      Neural Computation, 1994.                                   |
//+------------------------------------------------------------------+
void CMLPBase::MLPHessianBatch(CMultilayerPerceptron &network,
                               CMatrixDouble &xy,const int ssize,
                               double &e,double &grad[],
                               CMatrixDouble &h)
  {
   CRowDouble Grad;
//--- initialization
   e=0;
//--- function call
   MLPHessianBatchInternal(network,xy,ssize,false,e,Grad,h);
//--- return result
   Grad.ToArray(grad);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CMLPBase::MLPHessianBatch(CMultilayerPerceptron &network,
                               CMatrixDouble &xy,const int ssize,
                               double &e,CRowDouble &grad,
                               CMatrixDouble &h)
  {
//--- initialization
   e=0;
//--- function call
   MLPHessianBatchInternal(network,xy,ssize,false,e,grad,h);
  }
//+------------------------------------------------------------------+
//| Internal subroutine, shouldn't be called by user.                |
//+------------------------------------------------------------------+
void CMLPBase::MLPInternalProcessVector(int &structinfo[],double &weights[],
                                        double &columnmeans[],
                                        double &columnsigmas[],
                                        double &neurons[],
                                        double &dfdnet[],
                                        double &x[],double &y[])
  {
//--- create variables
   CRowInt StructInfo=structinfo;
   CRowDouble Weights=weights;
   CRowDouble ColumnMeans=columnmeans;
   CRowDouble ColumnSigmas=columnsigmas;
   CRowDouble X=x;
   CRowDouble Neurons;
   CRowDouble dFdnet;
   CRowDouble Y;
//--- function call
   MLPInternalProcessVector(StructInfo,Weights,ColumnMeans,ColumnSigmas,Neurons,dFdnet,X,Y);
//--- return result
   Y.ToArray(y);
   Neurons.ToArray(neurons);
   dFdnet.ToArray(dfdnet);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CMLPBase::MLPInternalProcessVector(CRowInt &structinfo,CRowDouble &weights,
                                        CRowDouble &columnmeans,
                                        CRowDouble &columnsigmas,
                                        CRowDouble &neurons,
                                        CRowDouble &dfdnet,
                                        CRowDouble &x,CRowDouble &y)
  {
//--- create variables
   int    i=0;
   int    n1=0;
   int    n2=0;
   int    w1=0;
   int    w2=0;
   int    ntotal=0;
   int    nin=0;
   int    nout=0;
   int    istart=0;
   int    offs=0;
   double net=0;
   double f=0;
   double df=0;
   double d2f=0;
   double mx=0;
   bool   perr;
   int    i_=0;
   int    i1_=0;
//--- Read network geometry
   nin=structinfo[1];
   nout=structinfo[2];
   ntotal=structinfo[3];
   istart=structinfo[5];
//--- Inputs standartisation and putting in the network
   for(i=0; i<nin; i++)
     {
      //--- check
      if(columnsigmas[i]!=0.0)
         neurons.Set(i,(x[i]-columnmeans[i])/columnsigmas[i]);
      else
         neurons.Set(i,x[i]-columnmeans[i]);
     }
//--- Allocate
   neurons.Resize(ntotal);
   dfdnet.Resize(ntotal);
   y.Resize(nout);
//--- Process network
   for(i=0; i<ntotal; i++)
     {
      offs=istart+i*m_nfieldwidth;
      //--- check
      if(structinfo[offs]>0 || structinfo[offs]==-5)
        {
         //--- Activation function
         MLPActivationFunction(neurons[structinfo[offs+2]],structinfo[offs],f,df,d2f);
         //--- change values
         neurons.Set(i,f);
         dfdnet.Set(i,df);
         continue;
        }
      //--- check
      if(structinfo[offs]==0)
        {
         //--- Adaptive summator
         n1=structinfo[offs+2];
         w1=structinfo[offs+3];
         w2=w1+structinfo[offs+1];
         i1_=(n1)-(w1);
         net=0.0;
         //--- calculation
         for(i_=w1; i_<w2; i_++)
            net+=weights[i_]*neurons[i_+i1_];
         neurons.Set(i,net);
         dfdnet.Set(i,1.0);
         continue;
        }
      //--- check
      if(structinfo[offs]<0)
        {
         perr=true;
         //--- check
         switch(structinfo[offs])
           {
            case -2:
               //--- input neuron,left unchanged
               perr=false;
               break;
            //--- check
            case -3:
               //--- "-1" neuron
               neurons.Set(i,-1);
               perr=false;
               break;
            //--- check
            case -4:
               //--- "0" neuron
               neurons.Set(i,0);
               perr=false;
               break;
           }
         //--- check
         if(!CAp::Assert(!perr,__FUNCTION__+": internal error - unknown neuron type!"))
            return;
         continue;
        }
     }
//--- Extract result
   i1_=ntotal-nout;
   for(i_=0; i_<nout; i_++)
      y.Set(i_,neurons[i_+i1_]);
//--- Softmax post-processing or standardisation if needed
   if(!CAp::Assert(structinfo[6]==0 || structinfo[6]==1,__FUNCTION__+": unknown normalization type!"))
      return;
//--- check
   if(structinfo[6]==1)
     {
      //--- Softmax
      mx=y.Max();
      //--- calculation
      y=MathExp(y-mx+0);
      net=y.Sum();
      y/= net;
     }
   else
     {
      //--- Standardisation
      for(i=0; i<nout; i++)
         y.Set(i,y[i]*columnsigmas[nin+i]+columnmeans[nin+i]);
     }
  }
//+------------------------------------------------------------------+
//| Serializer: allocation                                           |
//+------------------------------------------------------------------+
void CMLPBase::MLPAlloc(CSerializer &s,CMultilayerPerceptron &network)
  {
//--- create variables
   int    i=0;
   int    j=0;
   int    k=0;
   int    fkind=0;
   double threshold=0;
   double v0=0;
   double v1=0;
   int    nin=0;
   int    nout=0;
//--- initialization
   nin=network.m_hllayersizes[0];
   nout=network.m_hllayersizes[network.m_hllayersizes.Size()-1];
//--- preparation to serialize
   s.Alloc_Entry();
   s.Alloc_Entry();
   s.Alloc_Entry();
//--- function call
   CApServ::AllocIntegerArray(s,network.m_hllayersizes,-1);
   for(i=1; i<network.m_hllayersizes.Size(); i++)
     {
      for(j=0; j<network.m_hllayersizes[i]; j++)
        {
         //--- function call
         MLPGetNeuronInfo(network,i,j,fkind,threshold);
         //--- preparation to serialize
         s.Alloc_Entry();
         s.Alloc_Entry();
         for(k=0; k<network.m_hllayersizes[i-1]; k++)
            s.Alloc_Entry();
        }
     }
   for(j=0; j<nin; j++)
     {
      //--- function call
      MLPGetInputScaling(network,j,v0,v1);
      //--- preparation to serialize
      s.Alloc_Entry();
      s.Alloc_Entry();
     }
   for(j=0; j<nout; j++)
     {
      //--- function call
      MLPGetOutputScaling(network,j,v0,v1);
      //--- preparation to serialize
      s.Alloc_Entry();
      s.Alloc_Entry();
     }
  }
//+------------------------------------------------------------------+
//| Serializer: serialization                                        |
//+------------------------------------------------------------------+
void CMLPBase::MLPSerialize(CSerializer &s,CMultilayerPerceptron &network)
  {
//--- create variables
   int    i=0;
   int    j=0;
   int    k=0;
   int    fkind=0;
   double threshold=0;
   double v0=0;
   double v1=0;
   int    nin=0;
   int    nout=0;
//--- change values
   nin=network.m_hllayersizes[0];
   nout=network.m_hllayersizes[network.m_hllayersizes.Size()-1];
//--- serializetion
   s.Serialize_Int(CSCodes::GetMLPSerializationCode());
   s.Serialize_Int(m_mlpfirstversion);
   s.Serialize_Bool(MLPIsSoftMax(network));
//--- function call
   CApServ::SerializeIntegerArray(s,network.m_hllayersizes,-1);
   for(i=1; i<CAp::Len(network.m_hllayersizes); i++)
     {
      for(j=0; j<network.m_hllayersizes[i]; j++)
        {
         //--- function call
         MLPGetNeuronInfo(network,i,j,fkind,threshold);
         //--- serializetion
         s.Serialize_Int(fkind);
         s.Serialize_Double(threshold);
         for(k=0; k<network.m_hllayersizes[i-1]; k++)
            s.Serialize_Double(MLPGetWeight(network,i-1,k,i,j));
        }
     }
   for(j=0; j<nin; j++)
     {
      //--- function call
      MLPGetInputScaling(network,j,v0,v1);
      //--- serializetion
      s.Serialize_Double(v0);
      s.Serialize_Double(v1);
     }
   for(j=0; j<nout; j++)
     {
      //--- function call
      MLPGetOutputScaling(network,j,v0,v1);
      //--- serializetion
      s.Serialize_Double(v0);
      s.Serialize_Double(v1);
     }
  }
//+------------------------------------------------------------------+
//| Serializer: unserialization                                      |
//+------------------------------------------------------------------+
void CMLPBase::MLPUnserialize(CSerializer &s,CMultilayerPerceptron &network)
  {
//--- create variables
   int    i0=0;
   int    i1=0;
   int    i=0;
   int    j=0;
   int    k=0;
   int    fkind=0;
   double threshold=0;
   double v0=0;
   double v1=0;
   int    nin=0;
   int    nout=0;
   bool   issoftmax;
//--- create array
   CRowInt    layersizes;
//--- check correctness of header
   i0=s.Unserialize_Int();
//--- check
   if(!CAp::Assert(i0==CSCodes::GetMLPSerializationCode(),__FUNCTION__+": stream header corrupted"))
      return;
//--- unserializetion
   i1=s.Unserialize_Int();
//--- check
   if(!CAp::Assert(i1==m_mlpfirstversion,__FUNCTION__+": stream header corrupted"))
      return;
//--- Create network
   issoftmax=s.Unserialize_Bool();
//--- function call
   CApServ::UnserializeIntegerArray(s,layersizes);
//--- change values
   nin=layersizes[0];
   nout=layersizes[layersizes.Size()-1];
//--- check
   switch(layersizes.Size())
     {
      case 2:
         //--- check
         if(issoftmax)
            MLPCreateC0(layersizes[0],layersizes[1],network);
         else
            MLPCreate0(layersizes[0],layersizes[1],network);
         break;
      case 3:
         //--- check
         if(issoftmax)
            MLPCreateC1(layersizes[0],layersizes[1],layersizes[2],network);
         else
            MLPCreate1(layersizes[0],layersizes[1],layersizes[2],network);
         break;
      case 4:
         //--- check
         if(issoftmax)
            MLPCreateC2(layersizes[0],layersizes[1],layersizes[2],layersizes[3],network);
         else
            MLPCreate2(layersizes[0],layersizes[1],layersizes[2],layersizes[3],network);
         break;
      default:
         //--- check
         if(!CAp::Assert(layersizes.Size()==2 || layersizes.Size()==3 || layersizes.Size()==4,__FUNCTION__+": too many hidden layers!"))
            return;
     }
//--- Load neurons and weights
   for(i=1; i<layersizes.Size(); i++)
     {
      for(j=0; j<layersizes[i]; j++)
        {
         //--- unserializetion
         fkind=s.Unserialize_Int();
         threshold=s.Unserialize_Double();
         //--- function call
         MLPSetNeuronInfo(network,i,j,fkind,threshold);
         //--- unserializetion
         for(k=0; k<layersizes[i-1]; k++)
           {
            v0=s.Unserialize_Double();
            //--- function call
            MLPSetWeight(network,i-1,k,i,j,v0);
           }
        }
     }
//
//--- Load standartizator
//
   for(j=0; j<nin; j++)
     {
      //--- unserializetion
      v0=s.Unserialize_Double();
      v1=s.Unserialize_Double();
      //--- function call
      MLPSetInputScaling(network,j,v0,v1);
     }
   for(j=0; j<nout; j++)
     {
      //--- unserializetion
      v0=s.Unserialize_Double();
      v1=s.Unserialize_Double();
      //--- function call
      MLPSetOutputScaling(network,j,v0,v1);
     }
  }
//+------------------------------------------------------------------+
//| Calculation of all types of errors on subset of dataset.         |
//| INPUT PARAMETERS:                                                |
//|   Network  -  network initialized with one of the network        |
//|               creation funcs                                     |
//|   XY       -  original dataset; one sample = one row;            |
//|               first NIn columns contain inputs, next NOut        |
//|               columns - desired outputs.                         |
//|   SetSize  -  real size of XY, SetSize>=0;                       |
//|   Subset   -  subset of SubsetSize elements, array[SubsetSize];  |
//|   SubsetSize- number of elements in Subset[] array:              |
//|               * if SubsetSize>0, rows of XY with indices         |
//|                 Subset[0]......Subset[SubsetSize-1] are processed|
//|               * if SubsetSize=0, zeros are returned              |
//|               * if SubsetSize<0, entire dataset is  processed;   |
//|                 Subset[] array is ignored in this case.          |
//| OUTPUT PARAMETERS:                                               |
//|   Rep      -  it contains all type of errors.                    |
//+------------------------------------------------------------------+
void CMLPBase::MLPAllErrorsSubset(CMultilayerPerceptron &network,
                                  CMatrixDouble &xy,int setsize,
                                  CRowInt &subset,int subsetsize,
                                  CModelErrors &rep)
  {
//--- create variables
   int idx0=0;
   int idx1=0;
   int idxtype=0;
//--- check
   if(!CAp::Assert(xy.Rows()>=setsize,__FUNCTION__": XY has less than SetSize rows"))
      return;
   if(setsize>0)
     {
      if(MLPIsSoftMax(network))
        {
         if(!CAp::Assert(xy.Cols()>MLPGetInputsCount(network),__FUNCTION__": XY has less than NIn+1 columns"))
            return;
        }
      else
         if(!CAp::Assert(xy.Cols()>=MLPGetInputsCount(network)+MLPGetOutputsCount(network),__FUNCTION__": XY has less than NIn+NOut columns"))
            return;
     }
   if(subsetsize>=0)
     {
      idx0=0;
      idx1=subsetsize;
      idxtype=1;
     }
   else
     {
      idx0=0;
      idx1=setsize;
      idxtype=0;
     }
   MLPAllErrorsX(network,xy,network.m_dummysxy,setsize,0,subset,idx0,idx1,idxtype,rep);
  }
//+------------------------------------------------------------------+
//| Calculation of all types of errors on subset of dataset.         |
//| INPUT PARAMETERS:                                                |
//|   Network  -  network initialized with one of the network        |
//|               creation funcs                                     |
//|   XY       -  original dataset given by sparse matrix;           |
//|               one sample = one row; first NIn columns contain    |
//|               inputs, next NOut columns - desired outputs.       |
//|   SetSize  -  real size of XY, SetSize>=0;                       |
//|   Subset   -  subset of SubsetSize elements, array[SubsetSize];  |
//|   SubsetSize- number of elements in Subset[] array:              |
//|               * if SubsetSize>0, rows of XY with indices         |
//|                 Subset[0]......Subset[SubsetSize-1] are processed|
//|               * if SubsetSize=0, zeros are returned              |
//|               * if SubsetSize<0, entire dataset is processed;    |
//|                 Subset[] array is ignored in this case.          |
//| OUTPUT PARAMETERS:                                               |
//|   Rep      -  it contains all type of errors.                    |
//+------------------------------------------------------------------+
void CMLPBase::MLPAllErrorsSparseSubset(CMultilayerPerceptron &network,
                                        CSparseMatrix &xy,int setsize,
                                        CRowInt &subset,int subsetsize,
                                        CModelErrors &rep)
  {
//--- create variables
   int idx0=0;
   int idx1=0;
   int idxtype=0;
//--- check
   if(!CAp::Assert(CSparse::SparseIsCRS(xy),__FUNCTION__": XY is not in CRS format."))
      return;
   if(!CAp::Assert(CSparse::SparseGetNRows(xy)>=setsize,__FUNCTION__": XY has less than SetSize rows"))
      return;
   if(setsize>0)
     {
      if(MLPIsSoftMax(network))
        {
         if(!CAp::Assert(CSparse::SparseGetNCols(xy)>MLPGetInputsCount(network),__FUNCTION__": XY has less than NIn+1 columns"))
            return;
        }
      else
         if(!CAp::Assert(CSparse::SparseGetNCols(xy)>=MLPGetInputsCount(network)+MLPGetOutputsCount(network),__FUNCTION__": XY has less than NIn+NOut columns"))
            return;
     }
   if(subsetsize>=0)
     {
      idx0=0;
      idx1=subsetsize;
      idxtype=1;
     }
   else
     {
      idx0=0;
      idx1=setsize;
      idxtype=0;
     }
   MLPAllErrorsX(network,network.m_dummydxy,xy,setsize,1,subset,idx0,idx1,idxtype,rep);
  }
//+------------------------------------------------------------------+
//| Error of the neural network on subset of dataset.                |
//| INPUT PARAMETERS:                                                |
//|   Network  -  neural network;                                    |
//|   XY       -  training set, see below for information on the     |
//|               training set format;                               |
//|   SetSize  -  real size of XY, SetSize>=0;                       |
//|   Subset   -  subset of SubsetSize elements, array[SubsetSize];  |
//|   SubsetSize- number of elements in Subset[] array:              |
//|               * if SubsetSize>0, rows of XY with indices         |
//|                 Subset[0]......Subset[SubsetSize-1] are processed|
//|               * if SubsetSize=0, zeros are returned              |
//|               * if SubsetSize<0, entire dataset is  processed;   |
//|                 Subset[] array is ignored in this case.          |
//| RESULT:                                                          |
//|   sum-of-squares error, SUM(sqr(y[i]-desired_y[i])/2)            |
//| DATASET FORMAT:                                                  |
//|   This function uses two different dataset formats - one for     |
//|   regression networks, another one for classification networks.  |
//|   For regression networks with NIn inputs and NOut outputs       |
//|   following dataset format is used:                              |
//|      * dataset is given by NPoints*(NIn+NOut) matrix             |
//|      * each row corresponds to one example                       |
//|      * first NIn columns are inputs, next NOut columns are       |
//|        outputs                                                   |
//|   For classification networks with NIn inputs and NClasses clases|
//|   following dataset format is used:                              |
//|      * dataset is given by NPoints*(NIn+1) matrix                |
//|      * each row corresponds to one example                       |
//|      * first NIn columns are inputs, last column stores class    |
//|        number (from 0 to NClasses-1).                            |
//+------------------------------------------------------------------+
double CMLPBase::MLPErrorSubset(CMultilayerPerceptron &network,
                                CMatrixDouble &xy,int setsize,
                                CRowInt &subset,int subsetsize)
  {
//--- create variables
   double result=0;
   int    idx0=0;
   int    idx1=0;
   int    idxtype=0;
//--- check
   if(!CAp::Assert(xy.Rows()>=setsize,__FUNCTION__": XY has less than SetSize rows"))
      return(0);
   if(setsize>0)
     {
      if(MLPIsSoftMax(network))
        {
         if(!CAp::Assert(xy.Cols()>MLPGetInputsCount(network),__FUNCTION__": XY has less than NIn+1 columns"))
            return(0);
        }
      else
         if(!CAp::Assert(xy.Cols()>=MLPGetInputsCount(network)+MLPGetOutputsCount(network),__FUNCTION__": XY has less than NIn+NOut columns"))
            return(0);
     }
   if(subsetsize>=0)
     {
      idx0=0;
      idx1=subsetsize;
      idxtype=1;
     }
   else
     {
      idx0=0;
      idx1=setsize;
      idxtype=0;
     }
//--- function call
   MLPAllErrorsX(network,xy,network.m_dummysxy,setsize,0,subset,idx0,idx1,idxtype,network.m_err);
   result=CMath::Sqr(network.m_err.m_RMSError)*(idx1-idx0)*MLPGetOutputsCount(network)/2;
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| Error of the neural network on subset of sparse dataset.         |
//| INPUT PARAMETERS:                                                |
//|   Network  -  neural network;                                    |
//|   XY       -  training set, see below for information on the     |
//|               training set format. This function checks          |
//|               correctness of the dataset (no NANs/INFs, class    |
//|               numbers are correct) and throws exception when     |
//|               incorrect dataset is passed. Sparse matrix must use|
//|               CRS format for storage.                            |
//|   SetSize  -  real size of XY, SetSize>=0;  it is used when      |
//|               SubsetSize<0;                                      |
//|   Subset   -  subset of SubsetSize elements, array[SubsetSize];  |
//|   SubsetSize- number of elements in Subset[] array:              |
//|               * if SubsetSize>0, rows of XY with indices         |
//|                 Subset[0]......Subset[SubsetSize-1] are processed|
//|               * if SubsetSize=0, zeros are returned              |
//|               * if SubsetSize<0, entire dataset is processed;    |
//|                 Subset[] array is ignored in this case.          |
//| RESULT:                                                          |
//|   sum-of-squares error, SUM(sqr(y[i]-desired_y[i])/2)            |
//| DATASET FORMAT:                                                  |
//|   This function uses two different dataset formats - one for     |
//|   regression networks, another one for classification networks.  |
//|   For regression networks with NIn inputs and NOut outputs       |
//|   following dataset format is used:                              |
//|      * dataset is given by NPoints*(NIn+NOut) matrix             |
//|      * each row corresponds to one example                       |
//|      * first NIn columns are inputs, next NOut columns are       |
//|        outputs                                                   |
//|   For classification networks with NIn inputs and NClasses clases|
//|   following dataset format is used:                              |
//|      * dataset is given by NPoints*(NIn+1) matrix                |
//|      * each row corresponds to one example                       |
//|      * first NIn columns are inputs, last column stores class    |
//|        number (from 0 to NClasses-1).                            |
//+------------------------------------------------------------------+
double CMLPBase::MLPErrorSparseSubset(CMultilayerPerceptron &network,
                                      CSparseMatrix &xy,int setsize,
                                      CRowInt &subset,int subsetsize)
  {
//--- create variables
   double result=0;
   int    idx0=0;
   int    idx1=0;
   int    idxtype=0;
//--- check
   if(!CAp::Assert(CSparse::SparseIsCRS(xy),__FUNCTION__": XY is not in CRS format."))
      return(0);
   if(!CAp::Assert(CSparse::SparseGetNRows(xy)>=setsize,__FUNCTION__": XY has less than SetSize rows"))
      return(0);
   if(setsize>0)
     {
      if(MLPIsSoftMax(network))
        {
         if(!CAp::Assert(CSparse::SparseGetNCols(xy)>=MLPGetInputsCount(network)+1,__FUNCTION__": XY has less than NIn+1 columns"))
            return(0);
        }
      else
         if(!CAp::Assert(CSparse::SparseGetNCols(xy)>=MLPGetInputsCount(network)+MLPGetOutputsCount(network),__FUNCTION__": XY has less than NIn+NOut columns"))
            return(0);
     }
   if(subsetsize>=0)
     {
      idx0=0;
      idx1=subsetsize;
      idxtype=1;
     }
   else
     {
      idx0=0;
      idx1=setsize;
      idxtype=0;
     }
//--- function call
   MLPAllErrorsX(network,network.m_dummydxy,xy,setsize,1,subset,idx0,idx1,idxtype,network.m_err);
   result=CMath::Sqr(network.m_err.m_RMSError)*(idx1-idx0)*MLPGetOutputsCount(network)/2;
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| Calculation of all types of errors at once for a subset or full  |
//| dataset, which can be represented in different formats.          |
//| THIS INTERNAL FUNCTION IS NOT INTENDED TO BE USED BY ALGLIB USERS|
//+------------------------------------------------------------------+
void CMLPBase::MLPAllErrorsX(CMultilayerPerceptron &network,
                             CMatrixDouble &densexy,
                             CSparseMatrix &sparsexy,
                             int datasetsize,
                             int datasettype,
                             CRowInt &idx,
                             int subset0,
                             int subset1,
                             int subsettype,
                             CModelErrors &rep)
  {
//--- create variables
   int    nin=0;
   int    nout=0;
   int    wcount=0;
   int    rowsize=0;
   bool   iscls;
   int    srcidx=0;
   int    cstart=0;
   int    csize=0;
   int    len0=0;
   int    len1=0;
   double problemcost=0;
   CMLPBuffers pbuf;
   CModelErrors rep0;
   CModelErrors rep1;
//--- check
   if(!CAp::Assert(datasetsize>=0,__FUNCTION__": SetSize<0"))
      return;
   if(!CAp::Assert(datasettype==0 || datasettype==1,__FUNCTION__": DatasetType is incorrect"))
      return;
   if(!CAp::Assert(subsettype==0 || subsettype==1,__FUNCTION__": SubsetType is incorrect"))
      return;
//--- Determine network properties
   MLPProperties(network,nin,nout,wcount);
   iscls=MLPIsSoftMax(network);
//--- Retrieve and prepare
   if(iscls)
     {
      rowsize=nin+1;
      CBdSS::DSErrAllocate(nout,pbuf.m_Tmp0);
     }
   else
     {
      rowsize=nin+nout;
      CBdSS::DSErrAllocate(-nout,pbuf.m_Tmp0);
     }
//--- Processing
   CHPCCores::HPCPrepareChunkedGradient(network.m_weights,wcount,MLPNTotal(network),nin,nout,pbuf);
   cstart=subset0;
   while(cstart<subset1)
     {
      //--- Determine size of current chunk and copy it to PBuf.XY
      csize=MathMin(subset1,cstart+pbuf.m_ChunkSize)-cstart;
      for(int j=0; j<csize; j++)
        {
         srcidx=-1;
         if(subsettype==0)
            srcidx=cstart+j;
         else
            if(subsettype==1)
               srcidx=idx[cstart+j];
         if(!CAp::Assert(srcidx>=0,__FUNCTION__": internal error"))
            return;
         if(datasettype==0)
            pbuf.m_XY.Row(j,densexy,srcidx);
         else
            if(datasettype==1)
              {
               CSparse::SparseGetRow(sparsexy,srcidx,pbuf.m_XYRow);
               pbuf.m_XY.Row(j,pbuf.m_XYRow);
              }
        }
      //--- Unpack XY and process (temporary code, to be replaced by chunked processing)
      pbuf.m_XY2=pbuf.m_XY;
      MLPChunkedProcess(network,pbuf.m_XY2,0,csize,pbuf.m_Batch4Buf,pbuf.m_HPCBuf);
      ulong parts[]={nin,nout};
      matrix<double> splitted[];
      matrix<double> splitted2[];
      //--- Split
      pbuf.m_XY2.Split(parts,1,splitted2);
      if(iscls)
         parts[1]=1;
      pbuf.m_XY.Split(parts,1,splitted);
      for(int j=0; j<csize; j++)
        {
         pbuf.m_X=splitted2[0].Row(j);
         pbuf.m_Y=splitted2[1].Row(j);
         pbuf.m_Desiredy=splitted[1].Row(j);
         CBdSS::DSErrAccumulate(pbuf.m_Tmp0,pbuf.m_Y,pbuf.m_Desiredy);
        }
      //--- Process chunk and advance line pointer
      cstart+=pbuf.m_ChunkSize;
     }
   CBdSS::DSErrFinish(pbuf.m_Tmp0);
   rep.m_RelCLSError=pbuf.m_Tmp0[0];
   rep.m_AvgCE=pbuf.m_Tmp0[1]/MathLog(2);
   rep.m_RMSError=pbuf.m_Tmp0[2];
   rep.m_AvgError=pbuf.m_Tmp0[3];
   rep.m_AvgRelError=pbuf.m_Tmp0[4];
  }
//+------------------------------------------------------------------+
//| Internal subroutine: adding new input layer to network           |
//+------------------------------------------------------------------+
void CMLPBase::AddInputLayer(const int ncount,CRowInt &lsizes,
                             CRowInt &ltypes,CRowInt &lconnfirst,
                             CRowInt &lconnlast,int &lastproc)
  {
//--- change values
   lsizes.Set(0,ncount);
   ltypes.Set(0,-2);
   lconnfirst.Set(0,0);
   lconnlast.Set(0,0);
   lastproc=0;
  }
//+------------------------------------------------------------------+
//| Internal subroutine: adding new summator layer to network        |
//+------------------------------------------------------------------+
void CMLPBase::AddBiasedSummatorLayer(const int ncount,CRowInt &lsizes,
                                      CRowInt &ltypes,CRowInt &lconnfirst,
                                      CRowInt &lconnlast,int &lastproc)
  {
//--- change values
   lsizes.Set(lastproc+1,1);
   ltypes.Set(lastproc+1,-3);
   lconnfirst.Set(lastproc+1,0);
   lconnlast.Set(lastproc+1,0);
   lsizes.Set(lastproc+2,ncount);
   ltypes.Set(lastproc+2,0);
   lconnfirst.Set(lastproc+2,lastproc);
   lconnlast.Set(lastproc+2,lastproc+1);
   lastproc=lastproc+2;
  }
//+------------------------------------------------------------------+
//| Internal subroutine: adding new summator layer to network        |
//+------------------------------------------------------------------+
void CMLPBase::AddActivationLayer(const int functype,CRowInt &lsizes,
                                  CRowInt &ltypes,CRowInt &lconnfirst,
                                  CRowInt &lconnlast,int &lastproc)
  {
//--- check
   if(!CAp::Assert(functype>0 || functype==-5,__FUNCTION__+": incorrect function type"))
      return;
//--- change values
   lsizes.Set(lastproc+1,lsizes[lastproc]);
   ltypes.Set(lastproc+1,functype);
   lconnfirst.Set(lastproc+1,lastproc);
   lconnlast.Set(lastproc+1,lastproc);
   lastproc=lastproc+1;
  }
//+------------------------------------------------------------------+
//| Internal subroutine: adding new zero layer to network            |
//+------------------------------------------------------------------+
void CMLPBase::AddZeroLayer(CRowInt &lsizes,CRowInt &ltypes,
                            CRowInt &lconnfirst,CRowInt &lconnlast,
                            int &lastproc)
  {
//--- change values
   lsizes.Set(lastproc+1,1);
   ltypes.Set(lastproc+1,-4);
   lconnfirst.Set(lastproc+1,0);
   lconnlast.Set(lastproc+1,0);
   lastproc=lastproc+1;
  }
//+------------------------------------------------------------------+
//| This routine adds input layer to the high-level description of   |
//| the network.                                                     |
//| It modifies Network.HLConnections and Network.HLNeurons and      |
//| assumes that these arrays have enough place to store data.       |
//| It accepts following parameters:                                 |
//|     Network     -   network                                      |
//|     ConnIdx     -   index of the first free entry in the         |
//|                     HLConnections                                |
//|     NeuroIdx    -   index of the first free entry in the         |
//|                     HLNeurons                                    |
//|     StructInfoIdx-  index of the first entry in the low level    |
//|                     description of the current layer (in the     |
//|                     StructInfo array)                            |
//|     NIn         -   number of inputs                             |
//| It modified Network and indices.                                 |
//+------------------------------------------------------------------+
void CMLPBase::HLAddInputLayer(CMultilayerPerceptron &network,
                               int &connidx,int &neuroidx,
                               int &structinfoidx,int nin)
  {
//--- initialization
   int offs=m_hlm_nfieldwidth*neuroidx;
//--- change values
   for(int i=0; i<nin; i++)
     {
      network.m_hlneurons.Set(offs,0);
      network.m_hlneurons.Set(offs+1,i);
      network.m_hlneurons.Set(offs+2,-1);
      network.m_hlneurons.Set(offs+3,-1);
      offs=offs+m_hlm_nfieldwidth;
     }
//--- change values
   neuroidx=neuroidx+nin;
   structinfoidx=structinfoidx+nin;
  }
//+------------------------------------------------------------------+
//| This routine adds output layer to the high-level description of  |
//| the network.                                                     |
//| It modifies Network.HLConnections and Network. HLNeurons and     |
//| assumes that these arrays have enough place to store data. It    |
//| accepts following parameters:                                    |
//|     Network     -   network                                      |
//|     ConnIdx     -   index of the first free entry in the         |
//|                     HLConnections                                |
//|     NeuroIdx    -   index of the first free entry in the         |
//|                     HLNeurons                                    |
//|     StructInfoIdx-  index of the first entry in the low level    |
//|                     description of the current layer (in the     |
//|                     StructInfo array)                            |
//|     WeightsIdx  -   index of the first entry in the Weights      |
//|                     array which corresponds to the current layer |
//|     K           -   current layer index                          |
//|     NPrev       -   number of neurons in the previous layer      |
//|     NOut        -   number of outputs                            |
//|     IsCls       -   is it classifier network?                    |
//|     IsLinear    -   is it network with linear output?            |
//| It modified Network and ConnIdx/NeuroIdx/StructInfoIdx/WeightsIdx|
//+------------------------------------------------------------------+
void CMLPBase::HLAddOutputLayer(CMultilayerPerceptron &network,
                                int &connidx,int &neuroidx,
                                int &structinfoidx,int &weightsidx,
                                const int k,const int nprev,
                                const int nout,const bool iscls,
                                const bool islinearout)
  {
//--- create variables
   int i=0;
   int j=0;
   int neurooffs=0;
   int connoffs=0;
//--- check
   if(!CAp::Assert((iscls && islinearout) || !iscls,__FUNCTION__+": internal error"))
      return;
//--- initialization
   neurooffs=m_hlm_nfieldwidth*neuroidx;
   connoffs=m_hlconm_nfieldwidth*connidx;
//--- check
   if(!iscls)
     {
      //--- Regression network
      for(i=0; i<nout; i++)
        {
         //--- change values
         network.m_hlneurons.Set(neurooffs,k);
         network.m_hlneurons.Set(neurooffs+1,i);
         network.m_hlneurons.Set(neurooffs+2,structinfoidx+1+nout+i);
         network.m_hlneurons.Set(neurooffs+3,weightsidx+nprev+(nprev+1)*i);
         neurooffs=neurooffs+m_hlm_nfieldwidth;
        }
      for(i=0; i<nprev; i++)
        {
         for(j=0; j<nout; j++)
           {
            //--- change values
            network.m_hlconnections.Set(connoffs,k-1);
            network.m_hlconnections.Set(connoffs+1,i);
            network.m_hlconnections.Set(connoffs+2,k);
            network.m_hlconnections.Set(connoffs+3,j);
            network.m_hlconnections.Set(connoffs+4,weightsidx+i+j*(nprev+1));
            connoffs=connoffs+m_hlconm_nfieldwidth;
           }
        }
      //--- change values
      connidx=connidx+nprev*nout;
      neuroidx=neuroidx+nout;
      structinfoidx=structinfoidx+2*nout+1;
      weightsidx=weightsidx+nout*(nprev+1);
     }
   else
     {
      //--- Classification network
      for(i=0; i<nout-1; i++)
        {
         //--- change values
         network.m_hlneurons.Set(neurooffs,k);
         network.m_hlneurons.Set(neurooffs+1,i);
         network.m_hlneurons.Set(neurooffs+2,-1);
         network.m_hlneurons.Set(neurooffs+3,weightsidx+nprev+(nprev+1)*i);
         neurooffs=neurooffs+m_hlm_nfieldwidth;
        }
      //--- change values
      network.m_hlneurons.Set(neurooffs,k);
      network.m_hlneurons.Set(neurooffs+1,i);
      network.m_hlneurons.Set(neurooffs+2,-1);
      network.m_hlneurons.Set(neurooffs+3,-1);
      for(i=0; i<nprev; i++)
        {
         for(j=0; j<nout-1; j++)
           {
            //--- change values
            network.m_hlconnections.Set(connoffs,k-1);
            network.m_hlconnections.Set(connoffs+1,i);
            network.m_hlconnections.Set(connoffs+2,k);
            network.m_hlconnections.Set(connoffs+3,j);
            network.m_hlconnections.Set(connoffs+4,weightsidx+i+j*(nprev+1));
            connoffs=connoffs+m_hlconm_nfieldwidth;
           }
        }
      //--- change values
      connidx=connidx+nprev*(nout-1);
      neuroidx=neuroidx+nout;
      structinfoidx=structinfoidx+nout+2;
      weightsidx=weightsidx+(nout-1)*(nprev+1);
     }
  }
//+------------------------------------------------------------------+
//| This routine adds hidden layer to the high-level description of  |
//| the network.                                                     |
//| It modifies Network.HLConnections and Network.HLNeurons and      |
//| assumes that these arrays have enough place to store data. It    |
//| accepts following parameters:                                    |
//|     Network     -   network                                      |
//|     ConnIdx     -   index of the first free entry in the         |
//|                     HLConnections                                |
//|     NeuroIdx    -   index of the first free entry in the         |
//|                     HLNeurons                                    |
//|     StructInfoIdx-  index of the first entry in the low level    |
//|                     description of the current layer (in the     |
//|                     StructInfo array)                            |
//|     WeightsIdx  -   index of the first entry in the Weights      |
//|                     array which corresponds to the current layer |
//|     K           -   current layer index                          |
//|     NPrev       -   number of neurons in the previous layer      |
//|     NCur        -   number of neurons in the current layer       |
//| It modified Network and ConnIdx/NeuroIdx/StructInfoIdx/WeightsIdx|
//+------------------------------------------------------------------+
void CMLPBase::HLAddHiddenLayer(CMultilayerPerceptron &network,
                                int &connidx,int &neuroidx,
                                int &structinfoidx,int &weightsidx,
                                const int k,const int nprev,
                                const int ncur)
  {
//--- create variables
   int neurooffs=m_hlm_nfieldwidth*neuroidx;
   int connoffs=m_hlconm_nfieldwidth*connidx;

   for(int i=0; i<ncur; i++)
     {
      //--- change values
      network.m_hlneurons.Set(neurooffs,k);
      network.m_hlneurons.Set(neurooffs+1,i);
      network.m_hlneurons.Set(neurooffs+2,structinfoidx+1+ncur+i);
      network.m_hlneurons.Set(neurooffs+3,weightsidx+nprev+(nprev+1)*i);
      neurooffs=neurooffs+m_hlm_nfieldwidth;
     }
   for(int i=0; i<nprev; i++)
     {
      for(int j=0; j<ncur; j++)
        {
         //--- change values
         network.m_hlconnections.Set(connoffs,k-1);
         network.m_hlconnections.Set(connoffs+1,i);
         network.m_hlconnections.Set(connoffs+2,k);
         network.m_hlconnections.Set(connoffs+3,j);
         network.m_hlconnections.Set(connoffs+4,weightsidx+i+j*(nprev+1));
         connoffs=connoffs+m_hlconm_nfieldwidth;
        }
     }
//--- change values
   connidx=connidx+nprev*ncur;
   neuroidx=neuroidx+ncur;
   structinfoidx=structinfoidx+2*ncur+1;
   weightsidx=weightsidx+ncur*(nprev+1);
  }
//+------------------------------------------------------------------+
//| This function fills high level information about network created |
//| using internal MLPCreate() function.                             |
//| This function does NOT examine StructInfo for low level          |
//| information, it just expects that network has following          |
//| structure:                                                       |
//|     input neuron            \                                    |
//|     ...                      | input layer                       |
//|     input neuron            /                                    |
//|     "-1" neuron             \                                    |
//|     biased summator          |                                   |
//|     ...                      |                                   |
//|     biased summator          | hidden layer(s), if there are     |
//|     activation function      | exists any                        |
//|     ...                      |                                   |
//|     activation function     /                                    |
//|     "-1" neuron            \                                     |
//|     biased summator         | output layer:                      |
//|     ...                     | * we have NOut summators/activators|
//|     biased summator         |   for regression networks          |
//|     activation function     | * we have only NOut-1 summators and|
//|     ...                     |   no activators for classifiers    |
//|     activation function     |*we have "0" neuron only when we  |
//|     "0" neuron              /   have classifier                  |
//+------------------------------------------------------------------+
void CMLPBase::FillHighLevelInformation(CMultilayerPerceptron &network,
                                        const int nin,const int nhid1,
                                        const int nhid2,const int nout,
                                        const bool iscls,const bool islinearout)
  {
//--- create variables
   int idxweights=0;
   int idxstruct=0;
   int idxneuro=0;
   int idxconn=0;
//--- check
   if(!CAp::Assert((iscls && islinearout) || !iscls,__FUNCTION__+": internal error"))
      return;
//--- Preparations common to all types of networks
   network.m_hlnetworktype=0;
//--- network without hidden layers
   if(nhid1==0)
     {
      //--- allocation
      network.m_hllayersizes.Resize(2);
      //--- change values
      network.m_hllayersizes.Set(0,nin);
      network.m_hllayersizes.Set(1,nout);
      //--- check
      if(!iscls)
        {
         //--- allocation
         network.m_hlconnections.Resize(m_hlconm_nfieldwidth*nin*nout);
         network.m_hlneurons.Resize(m_hlm_nfieldwidth*(nin+nout));
         network.m_hlnormtype=0;
        }
      else
        {
         //--- allocation
         network.m_hlconnections.Resize(m_hlconm_nfieldwidth*nin*(nout-1));
         network.m_hlneurons.Resize(m_hlm_nfieldwidth*(nin+nout));
         network.m_hlnormtype=1;
        }
      //--- function call
      HLAddInputLayer(network,idxconn,idxneuro,idxstruct,nin);
      //--- function call
      HLAddOutputLayer(network,idxconn,idxneuro,idxstruct,idxweights,1,nin,nout,iscls,islinearout);
      //--- exit the function
      return;
     }
//--- network with one hidden layers
   if(nhid2==0)
     {
      //--- allocation
      network.m_hllayersizes.Resize(3);
      //--- change values
      network.m_hllayersizes.Set(0,nin);
      network.m_hllayersizes.Set(1,nhid1);
      network.m_hllayersizes.Set(2,nout);
      //--- check
      if(!iscls)
        {
         //--- allocation
         network.m_hlconnections.Resize(m_hlconm_nfieldwidth*(nin*nhid1+nhid1*nout));
         network.m_hlneurons.Resize(m_hlm_nfieldwidth*(nin+nhid1+nout));
         network.m_hlnormtype=0;
        }
      else
        {
         //--- allocation
         network.m_hlconnections.Resize(m_hlconm_nfieldwidth*(nin*nhid1+nhid1*(nout-1)));
         network.m_hlneurons.Resize(m_hlm_nfieldwidth*(nin+nhid1+nout));
         network.m_hlnormtype=1;
        }
      //--- function call
      HLAddInputLayer(network,idxconn,idxneuro,idxstruct,nin);
      //--- function call
      HLAddHiddenLayer(network,idxconn,idxneuro,idxstruct,idxweights,1,nin,nhid1);
      //--- function call
      HLAddOutputLayer(network,idxconn,idxneuro,idxstruct,idxweights,2,nhid1,nout,iscls,islinearout);
      //--- exit the function
      return;
     }
//--- Two hidden layers
   network.m_hllayersizes.Resize(4);
//--- change values
   network.m_hllayersizes.Set(0,nin);
   network.m_hllayersizes.Set(1,nhid1);
   network.m_hllayersizes.Set(2,nhid2);
   network.m_hllayersizes.Set(3,nout);
//--- check
   if(!iscls)
     {
      //--- allocation
      network.m_hlconnections.Resize(m_hlconm_nfieldwidth*(nin*nhid1+nhid1*nhid2+nhid2*nout));
      network.m_hlneurons.Resize(m_hlm_nfieldwidth*(nin+nhid1+nhid2+nout));
      network.m_hlnormtype=0;
     }
   else
     {
      //--- allocation
      network.m_hlconnections.Resize(m_hlconm_nfieldwidth*(nin*nhid1+nhid1*nhid2+nhid2*(nout-1)));
      network.m_hlneurons.Resize(m_hlm_nfieldwidth*(nin+nhid1+nhid2+nout));
      network.m_hlnormtype=1;
     }
//--- function call
   HLAddInputLayer(network,idxconn,idxneuro,idxstruct,nin);
//--- function call
   HLAddHiddenLayer(network,idxconn,idxneuro,idxstruct,idxweights,1,nin,nhid1);
//--- function call
   HLAddHiddenLayer(network,idxconn,idxneuro,idxstruct,idxweights,2,nhid1,nhid2);
//--- function call
   HLAddOutputLayer(network,idxconn,idxneuro,idxstruct,idxweights,3,nhid2,nout,iscls,islinearout);
  }
//+------------------------------------------------------------------+
//| Internal subroutine.                                             |
//+------------------------------------------------------------------+
void CMLPBase::MLPCreate(const int nin,const int nout,CRowInt &lsizes,
                         CRowInt &ltypes,CRowInt &lconnfirst,CRowInt &lconnlast,
                         const int layerscount,const bool isclsnet,
                         CMultilayerPerceptron &network)
  {
//--- create variables
   int i=0;
   int j=0;
   int ssize=0;
   int ntotal=0;
   int wcount=0;
   int offs=0;
   int nprocessed=0;
   int wallocated=0;
//--- creating arrays
   int localtemp[];
   int lnfirst[];
   int lnsyn[];
   CMLPBuffers buf;
   CSMLPGrad sgrad;
//--- Check
   if(!CAp::Assert(layerscount>0,__FUNCTION__+": wrong parameters!"))
      return;
//--- check
   if(!CAp::Assert(ltypes[0]==-2,__FUNCTION__+": wrong LTypes[0] (must be -2)!"))
      return;
   for(i=0; i<layerscount; i++)
     {
      //--- check
      if(!CAp::Assert(lsizes[i]>0,__FUNCTION__+": wrong LSizes!"))
         return;
      //--- check
      if(!CAp::Assert(lconnfirst[i]>=0 && (lconnfirst[i]<i || i==0),__FUNCTION__+": wrong LConnFirst!"))
         return;
      //--- check
      if(!CAp::Assert(lconnlast[i]>=lconnfirst[i] && (lconnlast[i]<i || i==0),__FUNCTION__+": wrong LConnLast!"))
         return;
     }
//--- Build network geometry
   ArrayResizeAL(lnfirst,layerscount);
   ArrayResizeAL(lnsyn,layerscount);
//--- initialization
   ntotal=0;
   wcount=0;
//--- calculation
   for(i=0; i<layerscount; i++)
     {
      //--- Analyze connections.
      //--- This code must throw an assertion in case of unknown LTypes[I]
      lnsyn[i]=-1;
      //--- check
      if(ltypes[i]>=0 || ltypes[i]==-5)
        {
         lnsyn[i]=0;
         for(j=lconnfirst[i]; j<=lconnlast[i]; j++)
            lnsyn[i]+=lsizes[j];
        }
      else
        {
         //--- check
         if((ltypes[i]==-2 || ltypes[i]==-3) || ltypes[i]==-4)
            lnsyn[i]=0;
        }
      //--- check
      if(!CAp::Assert(lnsyn[i]>=0,__FUNCTION__+": internal error #0!"))
         return;
      //--- Other info
      lnfirst[i]=ntotal;
      ntotal+=lsizes[i];
      //--- check
      if(ltypes[i]==0)
         wcount+=lnsyn[i]*lsizes[i];
     }
   ssize=7+ntotal*m_nfieldwidth;
//--- Allocate
   network.m_structinfo.Resize(ssize);
   network.m_weights.Resize(wcount);
//--- check
   if(isclsnet)
     {
      //--- allocation
      network.m_columnmeans.Resize(nin);
      network.m_columnsigmas.Resize(nin);
     }
   else
     {
      //--- allocation
      network.m_columnmeans.Resize(nin+nout);
      network.m_columnsigmas.Resize(nin+nout);
     }
//--- allocation
   network.m_neurons.Resize(ntotal);
   network.m_nwbuf.Resize(MathMax(wcount,2*nout));
   network.m_integerbuf.Resize(4);
   network.m_dfdnet.Resize(ntotal);
   network.m_x.Resize(nin);
   network.m_y.Resize(nout);
   network.m_derror.Resize(ntotal);
//--- Fill structure:
//--- * first, fill by dummy values to avoid spurious reports by Valgrind
//--- * then fill global info header
   network.m_structinfo.Fill(-999999);
   network.m_structinfo.Set(0,ssize);
   network.m_structinfo.Set(1,nin);
   network.m_structinfo.Set(2,nout);
   network.m_structinfo.Set(3,ntotal);
   network.m_structinfo.Set(4,wcount);
   network.m_structinfo.Set(5,7);
//--- check
   if(isclsnet)
      network.m_structinfo.Set(6,1);
   else
      network.m_structinfo.Set(6,0);
//--- Fill structure: neuron connections
   nprocessed=0;
   wallocated=0;
//--- calculation
   for(i=0; i<layerscount; i++)
     {
      for(j=0; j<lsizes[i]; j++)
        {
         offs=network.m_structinfo[5]+nprocessed*m_nfieldwidth;
         network.m_structinfo.Set(offs,ltypes[i]);
         //--- check
         if(ltypes[i]==0)
           {
            //--- Adaptive summator:
            //--- * connections with weights to previous neurons
            network.m_structinfo.Set(offs+1,lnsyn[i]);
            network.m_structinfo.Set(offs+2,lnfirst[lconnfirst[i]]);
            network.m_structinfo.Set(offs+3,wallocated);
            wallocated=wallocated+lnsyn[i];
            nprocessed=nprocessed+1;
           }
         //--- check
         if(ltypes[i]>0 || ltypes[i]==-5)
           {
            //--- Activation layer:
            //--- * each neuron connected to one (only one) of previous neurons.
            //--- * no weights
            network.m_structinfo.Set(offs+1,1);
            network.m_structinfo.Set(offs+2,lnfirst[lconnfirst[i]]+j);
            network.m_structinfo.Set(offs+3,-1);
            nprocessed=nprocessed+1;
           }
         //--- check
         if((ltypes[i]==-2 || ltypes[i]==-3) || ltypes[i]==-4)
            nprocessed++;
        }
     }
//--- check
   if(!CAp::Assert(wallocated==wcount,__FUNCTION__+": internal error #1!"))
      return;
//--- check
   if(!CAp::Assert(nprocessed==ntotal,__FUNCTION__+": internal error #2!"))
      return;
//--- Fill weights by small random values
//--- Initialize means and sigmas
   network.m_columnmeans.Fill(0);
   network.m_columnsigmas.Fill(1);
   MLPRandomize(network);
  }
//+------------------------------------------------------------------+
//| Internal subroutine for Hessian calculation.                     |
//| WARNING!!! Unspeakable math far beyong human capabilities :)     |
//+------------------------------------------------------------------+
void CMLPBase::MLPHessianBatchInternal(CMultilayerPerceptron &network,
                                       CMatrixDouble &xy,const int ssize,
                                       const bool naturalerr,double &e,
                                       CRowDouble &grad,CMatrixDouble &h)
  {
//--- create variables
   int    nin=0;
   int    nout=0;
   int    wcount=0;
   int    ntotal=0;
   int    istart=0;
   int    i=0;
   int    j=0;
   int    k=0;
   int    kl=0;
   int    offs=0;
   int    n1=0;
   int    n2=0;
   int    w1=0;
   int    w2=0;
   double s=0;
   double t=0;
   double v=0;
   double et=0;
   bool   bflag;
   double f=0;
   double df=0;
   double d2f=0;
   double deidyj=0;
   double mx=0;
   double q=0;
   double z=0;
   double s2=0;
   double expi=0;
   double expj=0;
   int    i_=0;
   int    i1_=0;
//--- creating arrays
   CRowDouble x;
   CRowDouble desiredy;
   CRowDouble gt;
//--- create matrix
   CMatrixDouble rx;
   CMatrixDouble ry;
   CMatrixDouble rdx;
   CMatrixDouble rdy;
   matrix<double> splittedXY[];
//--- initialization
   e=0;
//--- function call
   MLPProperties(network,nin,nout,wcount);
//--- initialization
   ntotal=network.m_structinfo[3];
   istart=network.m_structinfo[5];
//--- Prepare
   rdx.Resize(ntotal+nout,wcount);
   rdy.Resize(ntotal+nout,wcount);
//--- initialization
   e=0;
   grad=vector<double>::Zeros(wcount);
   h=matrix<double>::Zeros(wcount,wcount);
   ulong parts[2];
   parts[0]=nin;
   parts[1]=(MLPIsSoftMax(network)?1:nout);
   xy.Split(parts,1,splittedXY);
//--- Process
   for(k=0; k<ssize ; k++)
     {
      //--- Process vector with MLPGradN.
      //--- Now Neurons,DFDNET and DError contains results of the last run.
      x=splittedXY[0].Row(k);
      if(MLPIsSoftMax(network))
        {
         kl=(int)MathRound(splittedXY[1][k,0]);
         desiredy=vector<double>::Zeros(nout);
         desiredy.Set(kl,1);
        }
      else
         desiredy=splittedXY[1].Row(k);
      //--- check
      if(naturalerr)
         MLPGradN(network,x,desiredy,et,gt);
      else
         MLPGrad(network,x,desiredy,et,gt);
      //--- grad,error
      e+=et;
      grad+=gt;
      //--- Hessian.
      //--- Forward pass of the R-algorithm
      rx=matrix<double>::Zeros(ntotal,wcount);
      ry=rx;
      for(i=0; i<ntotal; i++)
        {
         offs=istart+i*m_nfieldwidth;
         //--- check
         if(network.m_structinfo[offs]>0 || network.m_structinfo[offs]==-5)
           {
            //--- Activation function
            n1=network.m_structinfo[offs+2];
            rx.Row(i,ry,n1);
            //--- calculation
            v=network.m_dfdnet[i];
            ry.Row(i,rx[i]*v);
            continue;
           }
         //--- check
         if(network.m_structinfo[offs]==0)
           {
            //--- Adaptive summator
            n1=network.m_structinfo[offs+2];
            n2=n1+network.m_structinfo[offs+1];
            w1=network.m_structinfo[offs+3];
            w2=w1+network.m_structinfo[offs+1];
            //--- calculation
            for(j=n1; j<n2; j++)
              {
               v=network.m_weights[w1+j-n1];
               rx.Row(i,rx[i]+ry[j]*v);
               rx.Add(i,w1+j-n1,network.m_neurons[j]);
              }
            ry.Row(i,rx,i);
            continue;
           }
         //--- check
         if(network.m_structinfo[offs]<0)
           {
            bflag=true;
            //--- check
            switch(network.m_structinfo[offs])
              {
               case -2:
                  //--- input neuron,left unchanged
                  bflag=false;
                  break;
               case -3:
                  //--- "-1" neuron,left unchanged
                  bflag=false;
                  break;
               case -4:
                  //--- "0" neuron,left unchanged
                  bflag=false;
                  break;
              }
            //--- check
            if(!CAp::Assert(!bflag,__FUNCTION__+": internal error - unknown neuron type!"))
               return;
            continue;
           }
        }
      //--- Hessian. Backward pass of the R-algorithm.
      //--- Stage 1. Initialize RDY
      rdy=matrix<double>::Zeros(ntotal+nout,wcount);
      rx.Resize(ntotal+nout,wcount);
      ry.Resize(ntotal+nout,wcount);
      //--- check
      if(network.m_structinfo[6]==0)
        {
         //--- Standardisation.
         //--- In context of the Hessian calculation standardisation
         //--- is considered as additional layer with weightless
         //--- activation function:
         //--- F(NET) :=Sigma*NET
         //--- So we add one more layer to forward pass,and
         //--- make forward/backward pass through this layer.
         for(i=0; i<nout; i++)
           {
            n1=ntotal-nout+i;
            n2=ntotal+i;
            //--- Forward pass from N1 to N2
            rx.Row(n2,ry,n1);
            v=network.m_columnsigmas[nin+i];
            ry.Row(n2,rx[n2]*v);
            //--- Initialization of RDY
            rdy.Row(n2,ry,n2);
            //--- Backward pass from N2 to N1:
            //--- 1. Calculate R(dE/dX).
            //--- 2. No R(dE/dWij) is needed since weight of activation neuron
            //---    is fixed to 1. So we can update R(dE/dY) for
            //---    the connected neuron (note that Vij=0,Wij=1)
            df=network.m_columnsigmas[nin+i];
            rdx.Row(n2,rdy[n2]*df);
            rdy.Row(n1,rdy[n1]+rdx[n2]);
           }
        }
      else
        {
         //--- Softmax.
         //--- Initialize RDY using generalized expression for ei'(yi)
         //--- (see expression (9) from p. 5 of "Fast Exact Multiplication by the Hessian").
         //--- When we are working with softmax network,generalized
         //--- expression for ei'(yi) is used because softmax
         //--- normalization leads to ei,which depends on all y's
         if(naturalerr)
           {
            //--- softmax + cross-entropy.
            //--- We have:
            //--- S=sum(exp(yk)),
            //--- ei=sum(trn)*exp(yi)/S-trn_i
            //--- j=i:   d(ei)/d(yj)=T*exp(yi)*(S-exp(yi))/S^2
            //--- j<>i:  d(ei)/d(yj)=-T*exp(yi)*exp(yj)/S^2
            t=desiredy.Sum();
            mx=network.m_neurons[ntotal-nout];
            //--- calculation
            for(i=1; i<nout; i++)
               mx=MathMax(mx,network.m_neurons[ntotal-nout+i]);
            s=0;
            for(i=0; i<nout; i++)
              {
               network.m_nwbuf.Set(i,MathExp(network.m_neurons[ntotal-nout+i]-mx));
               s=s+network.m_nwbuf[i];
              }
            //--- calculation
            for(i=0; i<nout; i++)
              {
               for(j=0; j<nout; j++)
                 {
                  //--- check
                  if(j==i)
                    {
                     deidyj=t*network.m_nwbuf[i]*(s-network.m_nwbuf[i])/CMath::Sqr(s);
                     rdy.Row(ntotal-nout+i,rdy[ntotal-nout+i]+ry[ntotal-nout+i]*deidyj);
                    }
                  else
                    {
                     deidyj=-(t*network.m_nwbuf[i]*network.m_nwbuf[j]/CMath::Sqr(s));
                     rdy.Row(ntotal-nout+i,rdy[ntotal-nout+i]+ry[ntotal-nout+j]*deidyj);
                    }
                 }
              }
           }
         else
           {
            //--- For a softmax + squared error we have expression
            //--- far beyond human imagination so we dont even try
            //--- to comment on it. Just enjoy the code...
            //--- P.S. That's why "natural error" is called "natural" -
            //--- compact beatiful expressions,fast code....
            mx=network.m_neurons[ntotal-nout];
            for(i=1; i<nout; i++)
               mx=MathMax(mx,network.m_neurons[ntotal-nout+i]);
            //--- calculation
            s=0;
            s2=0;
            for(i=0; i<nout; i++)
              {
               network.m_nwbuf.Set(i,MathExp(network.m_neurons[ntotal-nout+i]-mx));
               s+=network.m_nwbuf[i];
               s2+=CMath::Sqr(network.m_nwbuf[i]);
              }
            //--- calculation
            q=0;
            for(i=0; i<nout; i++)
               q+=(network.m_y[i]-desiredy[i])*network.m_nwbuf[i];
            for(i=0; i<nout; i++)
              {
               //--- change values
               z=-q+(network.m_y[i]-desiredy[i])*s;
               expi=network.m_nwbuf[i];
               for(j=0; j<nout; j++)
                 {
                  expj=network.m_nwbuf[j];
                  //--- check
                  if(j==i)
                     deidyj=expi/CMath::Sqr(s)*((z+expi)*(s-2*expi)/s+expi*s2/CMath::Sqr(s));
                  else
                     deidyj=expi*expj/CMath::Sqr(s)*(s2/CMath::Sqr(s)-2*z/s-(expi+expj)/s+(network.m_y[i]-desiredy[i])-(network.m_y[j]-desiredy[j]));
                  rdy.Row(ntotal-nout+i,rdy[ntotal-nout+i]+ry[ntotal-nout+j]*deidyj);
                 }
              }
           }
        }
      //--- Hessian. Backward pass of the R-algorithm
      //--- Stage 2. Process.
      for(i=ntotal-1; i>=0; i--)
        {
         //--- Possible variants:
         //--- 1. Activation function
         //--- 2. Adaptive summator
         //--- 3. Special neuron
         offs=istart+i*m_nfieldwidth;
         //--- check
         if(network.m_structinfo[offs]>0 || network.m_structinfo[offs]==-5)
           {
            n1=network.m_structinfo[offs+2];
            //--- First,calculate R(dE/dX).
            MLPActivationFunction(network.m_neurons[n1],network.m_structinfo[offs],f,df,d2f);
            v=d2f*network.m_derror[i];
            rdx.Row(i,rdy[i]*df);
            rdx.Row(i,rdx[i]+rx[i]*v);
            //--- No R(dE/dWij) is needed since weight of activation neuron
            //--- is fixed to 1.
            //--- So we can update R(dE/dY) for the connected neuron.
            //--- (note that Vij=0,Wij=1)
            rdy.Row(n1,rdy[n1]+rdx[i]);
            continue;
           }
         //--- check
         if(network.m_structinfo[offs]==0)
           {
            //--- Adaptive summator
            n1=network.m_structinfo[offs+2];
            n2=n1+network.m_structinfo[offs+1]-1;
            w1=network.m_structinfo[offs+3];
            w2=w1+network.m_structinfo[offs+1]-1;
            //--- First,calculate R(dE/dX).
            rdx.Row(i,rdy,i);
            //--- Then,calculate R(dE/dWij)
            for(j=w1; j<=w2; j++)
              {
               v=network.m_neurons[n1+j-w1];
               h.Row(j,h[j]+rdx[i]*v);
               //--- calculation
               v=network.m_derror[i];
               h.Row(j,h[j]+ry[n1+j-w1]*v);
              }
            //--- And finally,update R(dE/dY) for connected neurons.
            for(j=w1; j<=w2; j++)
              {
               v=network.m_weights[j];
               rdy.Row(n1+j-w1,rdy[n1+j-w1]+ rdx[i]*v);
               rdy.Set(n1+j-w1,j,rdy[n1+j-w1][j]+network.m_derror[i]);
              }
            continue;
           }
         //--- check
         if(network.m_structinfo[offs]<0)
           {
            bflag=false;
            //--- check
            if((network.m_structinfo[offs]==-2 || network.m_structinfo[offs]==-3) || network.m_structinfo[offs]==-4)
              {
               //--- Special neuron type,no back-propagation required
               bflag=true;
              }
            //--- check
            if(!CAp::Assert(bflag,__FUNCTION__+": unknown neuron type!"))
               return;
            continue;
           }
        }
     }
  }
//+------------------------------------------------------------------+
//| Internal subroutine                                              |
//| Network must be processed by MLPProcess on X                     |
//+------------------------------------------------------------------+
void CMLPBase::MLPInternalCalculateGradient(CMultilayerPerceptron &network,
                                            CRowDouble &neurons,
                                            CRowDouble &weights,
                                            CRowDouble &derror,
                                            CRowDouble &grad,
                                            const bool naturalerrorfunc)
  {
//--- create variables
   int    i=0;
   int    n1=0;
   int    n2=0;
   int    w1=0;
   int    w2=0;
   int    ntotal=network.m_structinfo[3];
   int    istart=network.m_structinfo[5];
   int    nin=network.m_structinfo[1];
   int    nout=network.m_structinfo[2];
   int    offs=0;
   double dedf=0;
   double dfdnet=0;
   double v=0;
   double fown=0;
   double deown=0;
   double net=0;
   double mx=0;
   bool   bflag;
   int    i_=0;
   int    i1_=0;
//--- Pre-processing of dError/dOut:
//--- from dError/dOut(normalized) to dError/dOut(non-normalized)
   if(!CAp::Assert(network.m_structinfo[6]==0 || network.m_structinfo[6]==1,__FUNCTION__+": unknown normalization type!"))
      return;
//--- check
   if(network.m_structinfo[6]==1)
     {
      //--- Softmax
      if(!naturalerrorfunc)
        {
         mx=network.m_neurons[ntotal-nout];
         for(i=1; i<nout; i++)
            mx=MathMax(mx,network.m_neurons[ntotal-nout+i]);
         net=0;
         for(i=0; i<nout; i++)
           {
            network.m_nwbuf.Set(i,MathExp(network.m_neurons[ntotal-nout+i]-mx));
            net+=network.m_nwbuf[i];
           }
         //--- calculation
         i1_=ntotal-nout;
         v=0.0;
         for(i=0; i<nout; i++)
            v+=network.m_derror[i+i1_]*network.m_nwbuf[i];
         for(i=0; i<nout; i++)
           {
            fown=network.m_nwbuf[i];
            deown=network.m_derror[i+i1_];
            network.m_nwbuf.Set(nout+i,(-v+deown*fown+deown*(net-fown))*fown/CMath::Sqr(net));
            network.m_derror.Set(i+i1_,network.m_nwbuf[nout+i]);
           }
        }
     }
   else
     {
      //--- Un-standardisation
      for(i=0; i<nout; i++)
         network.m_derror.Set(ntotal-nout+i,network.m_derror[ntotal-nout+i]*network.m_columnsigmas[nin+i]);
     }
//--- Backpropagation
   for(i=ntotal-1; i>=0; i--)
     {
      //--- Extract info
      offs=istart+i*m_nfieldwidth;
      //--- check
      if(network.m_structinfo[offs]>0 || network.m_structinfo[offs]==-5)
        {
         //--- Activation function
         dedf=network.m_derror[i];
         dfdnet=network.m_dfdnet[i];
         derror.Set(network.m_structinfo[offs+2],derror[network.m_structinfo[offs+2]]+dedf*dfdnet);
         continue;
        }
      //--- check
      if(network.m_structinfo[offs]==0)
        {
         //--- Adaptive summator
         n1=network.m_structinfo[offs+2];
         n2=n1+network.m_structinfo[offs+1];
         w1=network.m_structinfo[offs+3];
         w2=w1+network.m_structinfo[offs+1];
         dedf=network.m_derror[i];
         dfdnet=1.0;
         v=dedf*dfdnet;
         i1_=n1-w1;
         //--- calculation
         for(i_=w1; i_<w2; i_++)
            grad.Set(i_,v*neurons[i_+i1_]);
         i1_=w1-n1;
         for(i_=n1; i_<n2; i_++)
            derror.Add(i_,v*weights[i_+i1_]);
         continue;
        }
      //--- check
      if(network.m_structinfo[offs]<0)
        {
         bflag=false;
         //--- check
         if((network.m_structinfo[offs]==-2 || network.m_structinfo[offs]==-3) || network.m_structinfo[offs]==-4)
           {
            //--- Special neuron type,no back-propagation required
            bflag=true;
           }
         //--- check
         if(!CAp::Assert(bflag,__FUNCTION__+": unknown neuron type!"))
            return;
         continue;
        }
     }
  }
//+------------------------------------------------------------------+
//| Internal subroutine, chunked gradient                            |
//+------------------------------------------------------------------+
void CMLPBase::MLPChunkedGradient(CMultilayerPerceptron &network,
                                  CMatrixDouble &xy,const int cstart,
                                  const int csize,CRowDouble &batch4buf,
                                  CRowDouble &hpcbuf,
                                  double &e,const bool naturalerrorfunc)
  {
//--- create variables
   int    i=0;
   int    j=0;
   int    k=0;
   int    kl=0;
   int    ntotal=0;
   int    nin=0;
   int    nout=0;
   int    offs=0;
   double f=0;
   double df=0;
   double d2f=0;
   double v=0;
   double vv=0;
   double s=0;
   double fown=0;
   double deown=0;
   bool   bflag;
   int    istart=0;
   int    entrysize=0;
   int    dfoffs=0;
   int    derroroffs=0;
   int    entryoffs=0;
   int    neuronidx=0;
   int    srcentryoffs=0;
   int    srcneuronidx=0;
   int    srcweightidx=0;
   int    neurontype=0;
   int    nweights=0;
   int    offs0=0;
   int    offs1=0;
   int    offs2=0;
   double v0=0;
   double v1=0;
   double v2=0;
   double v3=0;
   double s0=0;
   double s1=0;
   double s2=0;
   double s3=0;
   int    chunksize=4;
//--- check
   if(!CAp::Assert(csize<=chunksize,__FUNCTION__": internal error (CSize>ChunkSize)"))
      return;
//--- Read network geometry, prepare data
   nin=network.m_structinfo[1];
   nout=network.m_structinfo[2];
   ntotal=network.m_structinfo[3];
   istart=network.m_structinfo[5];
   entrysize=12;
   dfoffs=4;
   derroroffs=8;
//--- Fill Batch4Buf by zeros.
//--- THIS STAGE IS VERY IMPORTANT!
//--- We fill all components of entry - neuron values, dF/dNET, dError/dF.
//--- It allows us to easily handle  situations  when  CSize<ChunkSize  by
//--- simply  working  with  ALL  components  of  Batch4Buf,  without ever
//--- looking at CSize. The idea is that dError/dF for  absent  components
//--- will be initialized by zeros - and won't be  rewritten  by  non-zero
//--- values during backpropagation.
   batch4buf.Fill(0);
//--- Forward pass:
//--- 1. Load data into Batch4Buf. If CSize<ChunkSize, data are padded by zeros.
//--- 2. Perform forward pass through network
   for(i=0; i<nin; i++)
     {
      entryoffs=entrysize*i;
      for(j=0; j<csize; j++)
        {
         if(network.m_columnsigmas[i]!=0.0)
            batch4buf.Set(entryoffs+j,(xy.Get(cstart+j,i)-network.m_columnmeans[i])/network.m_columnsigmas[i]);
         else
            batch4buf.Set(entryoffs+j,xy.Get(cstart+j,i)-network.m_columnmeans[i]);
        }
     }
   for(neuronidx=0; neuronidx<ntotal; neuronidx++)
     {
      entryoffs=entrysize*neuronidx;
      offs=istart+neuronidx*m_nfieldwidth;
      neurontype=network.m_structinfo[offs+0];
      if(neurontype>0 || neurontype==-5)
        {
         //--- "activation function" neuron, which takes value of neuron SrcNeuronIdx
         //--- and applies activation function to it.
         //--- This neuron has no weights and no tunable parameters.
         srcneuronidx=network.m_structinfo[offs+2];
         srcentryoffs=entrysize*srcneuronidx;
         for(i=0; i<4; i++)
           {
            MLPActivationFunction(batch4buf[srcentryoffs+i],neurontype,f,df,d2f);
            batch4buf.Set(entryoffs+i,f);
            batch4buf.Set(entryoffs+dfoffs+i,df);
           }
         continue;
        }
      if(neurontype==0)
        {
         //--- "adaptive summator" neuron, whose output is a weighted sum of inputs.
         //--- It has weights, but has no activation function.
         nweights=network.m_structinfo[offs+1];
         srcneuronidx=network.m_structinfo[offs+2];
         srcentryoffs=entrysize*srcneuronidx;
         srcweightidx=network.m_structinfo[offs+3];
         v0=0;
         v1=0;
         v2=0;
         v3=0;
         for(j=0; j<nweights; j++)
           {
            v=network.m_weights[srcweightidx];
            srcweightidx++;
            v0+=v*batch4buf[srcentryoffs];
            v1+=v*batch4buf[srcentryoffs+1];
            v2+=v*batch4buf[srcentryoffs+2];
            v3+=v*batch4buf[srcentryoffs+3];
            srcentryoffs+=entrysize;
           }
         batch4buf.Set(entryoffs,v0);
         batch4buf.Set(entryoffs+1,v1);
         batch4buf.Set(entryoffs+2,v2);
         batch4buf.Set(entryoffs+3,v3);
         batch4buf.Set(entryoffs+dfoffs,1);
         batch4buf.Set(entryoffs+1+dfoffs,1);
         batch4buf.Set(entryoffs+2+dfoffs,1);
         batch4buf.Set(entryoffs+3+dfoffs,1);
         continue;
        }
      if(neurontype<0)
        {
         bflag=false;
         switch(neurontype)
           {
            case -2:
               //--- Input neuron, left unchanged
               bflag=true;
               break;
            case -3:
               //--- "-1" neuron
               batch4buf.Set(entryoffs,-1);
               batch4buf.Set(entryoffs+1,-1);
               batch4buf.Set(entryoffs+2,-1);
               batch4buf.Set(entryoffs+3,-1);
               batch4buf.Set(entryoffs+dfoffs,0);
               batch4buf.Set(entryoffs+1+dfoffs,0);
               batch4buf.Set(entryoffs+2+dfoffs,0);
               batch4buf.Set(entryoffs+3+dfoffs,0);
               bflag=true;
               break;
            case -4:
               //--- "0" neuron
               batch4buf.Set(entryoffs,0);
               batch4buf.Set(entryoffs+1,0);
               batch4buf.Set(entryoffs+2,0);
               batch4buf.Set(entryoffs+3,0);
               batch4buf.Set(entryoffs+dfoffs,0);
               batch4buf.Set(entryoffs+1+dfoffs,0);
               batch4buf.Set(entryoffs+2+dfoffs,0);
               batch4buf.Set(entryoffs+3+dfoffs,0);
               bflag=true;
               break;
           }
         if(!CAp::Assert(bflag,__FUNCTION__": internal error - unknown neuron type!"))
            return;
         continue;
        }
     }
//--- Intermediate phase between forward and backward passes.
//--- For regression networks:
//--- * forward pass is completely done (no additional post-processing is
//---   needed).
//--- * before starting backward pass, we have to  calculate  dError/dOut
//---   for output neurons. We also update error at this phase.
//--- For classification networks:
//--- * in addition to forward pass we  apply  SOFTMAX  normalization  to
//---   output neurons.
//--- * after applying normalization, we have to  calculate  dError/dOut,
//---   which is calculated in two steps:
//---   * first, we calculate derivative of error with respect to SOFTMAX
//---     normalized outputs (normalized dError)
//---   * then,  we calculate derivative of error with respect to  values
//---     of outputs BEFORE normalization was applied to them
   if(!CAp::Assert(network.m_structinfo[6]==0 || network.m_structinfo[6]==1,__FUNCTION__": unknown normalization type!"))
      return;
   if(network.m_structinfo[6]==1)
     {
      //--- SOFTMAX-normalized network.
      //--- First,  calculate (V0,V1,V2,V3)  -  component-wise  maximum
      //--- of output neurons. This vector of maximum  values  will  be
      //--- used for normalization  of  outputs  prior  to  calculating
      //--- exponentials.
      //--- NOTE: the only purpose of this stage is to prevent overflow
      //---       during calculation of exponentials.  With  this stage
      //---       we  make  sure  that  all exponentials are calculated
      //---       with non-positive argument. If you load (0,0,0,0)  to
      //---       (V0,V1,V2,V3), your program will continue  working  -
      //---       although with less robustness.
      entryoffs=entrysize*(ntotal-nout);
      v0=batch4buf[entryoffs];
      v1=batch4buf[entryoffs+1];
      v2=batch4buf[entryoffs+2];
      v3=batch4buf[entryoffs+3];
      entryoffs+=entrysize;
      for(i=1; i<nout; i++)
        {
         v=batch4buf[entryoffs];
         if(v>v0)
            v0=v;
         v=batch4buf[entryoffs+1];
         if(v>v1)
            v1=v;
         v=batch4buf[entryoffs+2];
         if(v>v2)
            v2=v;
         v=batch4buf[entryoffs+3];
         if(v>v3)
            v3=v;
         entryoffs=entryoffs+entrysize;
        }
      //--- Then,  calculate exponentials and place them to part of the
      //--- array which  is  located  past  the  last  entry.  We  also
      //--- calculate sum of exponentials which will be stored past the
      //--- exponentials.
      entryoffs=entrysize*(ntotal-nout);
      offs0=entrysize*ntotal;
      s0=0;
      s1=0;
      s2=0;
      s3=0;
      for(i=0; i<nout; i++)
        {
         v=MathExp(batch4buf[entryoffs]-v0);
         s0+=v;
         batch4buf.Set(offs0,v);
         v=MathExp(batch4buf[entryoffs+1]-v1);
         s1+=v;
         batch4buf.Set(offs0+1,v);
         v=MathExp(batch4buf[entryoffs+2]-v2);
         s2+=v;
         batch4buf.Set(offs0+2,v);
         v=MathExp(batch4buf[entryoffs+3]-v3);
         s3+=v;
         batch4buf.Set(offs0+3,v);
         entryoffs=entryoffs+entrysize;
         offs0=offs0+chunksize;
        }
      offs0=entrysize*ntotal+2*nout*chunksize;
      batch4buf.Set(offs0,s0);
      batch4buf.Set(offs0+1,s1);
      batch4buf.Set(offs0+2,s2);
      batch4buf.Set(offs0+3,s3);
      //--- Now we have:
      //--- * Batch4Buf[0...EntrySize*NTotal-1] stores:
      //---   * NTotal*ChunkSize neuron output values (SOFTMAX normalization
      //---     was not applied to these values),
      //---   * NTotal*ChunkSize values of dF/dNET (derivative of neuron
      //---     output with respect to its input)
      //---   * NTotal*ChunkSize zeros in the elements which correspond to
      //---     dError/dOut (derivative of error with respect to neuron output).
      //--- * Batch4Buf[EntrySize*NTotal...EntrySize*NTotal+ChunkSize*NOut-1] -
      //---   stores exponentials of last NOut neurons.
      //--- * Batch4Buf[EntrySize*NTotal+ChunkSize*NOut-1...EntrySize*NTotal+ChunkSize*2*NOut-1]
      //---   - can be used for temporary calculations
      //--- * Batch4Buf[EntrySize*NTotal+ChunkSize*2*NOut...EntrySize*NTotal+ChunkSize*2*NOut+ChunkSize-1]
      //---   - stores sum-of-exponentials
      //--- Block below calculates derivatives of error function with respect
      //--- to non-SOFTMAX-normalized output values of last NOut neurons.
      //--- It is quite complicated; we do not describe algebra behind it,
      //--- but if you want you may check it yourself :)
      if(naturalerrorfunc)
        {
         //--- Calculate  derivative  of  error  with respect to values of
         //--- output  neurons  PRIOR TO SOFTMAX NORMALIZATION. Because we
         //--- use natural error function (cross-entropy), we  can  do  so
         //--- very easy.
         offs0=entrysize*ntotal+2*nout*chunksize;
         for(k=0; k<csize; k++)
           {
            s=batch4buf[offs0+k];
            kl=(int)MathRound(xy.Get(cstart+k,nin));
            offs1=(ntotal-nout)*entrysize+derroroffs+k;
            offs2=entrysize*ntotal+k;
            for(i=0; i<nout; i++)
              {
               v=(double)(i==kl);
               vv=batch4buf[offs2];
               batch4buf.Set(offs1,vv/s-v);
               e+=SafeCrossEntropy(v,vv/s);
               offs1+=entrysize;
               offs2+=chunksize;
              }
           }
        }
      else
        {
         //--- SOFTMAX normalization makes things very difficult.
         //--- Sorry, we do not dare to describe this esoteric math
         //--- in details.
         offs0=entrysize*ntotal+chunksize*2*nout;
         for(k=0; k<csize; k++)
           {
            s=batch4buf[offs0+k];
            kl=(int)MathRound(xy.Get(cstart+k,nin));
            vv=0;
            offs1=entrysize*ntotal+k;
            offs2=entrysize*ntotal+nout*chunksize+k;
            for(i=0; i<nout; i++)
              {
               fown=batch4buf[offs1];
               if(i==kl)
                  deown=fown/s-1;
               else
                  deown=fown/s;
               batch4buf.Set(offs2,deown);
               vv+=deown*fown;
               e+=deown*deown/2;
               offs1+=chunksize;
               offs2+=chunksize;
              }
            offs1=entrysize*ntotal+k;
            offs2=entrysize*ntotal+nout*chunksize+k;
            for(i=0; i<nout; i++)
              {
               fown=batch4buf[offs1];
               deown=batch4buf[offs2];
               batch4buf.Set((ntotal-nout+i)*entrysize+derroroffs+k,(-vv+deown*fown+deown*(s-fown))*fown/CMath::Sqr(s));
               offs1+=chunksize;
               offs2+=chunksize;
              }
           }
        }
     }
   else
     {
      //--- Regression network with sum-of-squares function.
      //--- For each NOut of last neurons:
      //--- * calculate difference between actual and desired output
      //--- * calculate dError/dOut for this neuron (proportional to difference)
      //--- * store in in last 4 components of entry (these values are used
      //---   to start backpropagation)
      //--- * update error
      for(i=0; i<nout; i++)
        {
         v0=network.m_columnsigmas[nin+i];
         v1=network.m_columnmeans[nin+i];
         entryoffs=entrysize*(ntotal-nout+i);
         offs0=entryoffs;
         offs1=entryoffs+derroroffs;
         for(j=0; j<csize; j++)
           {
            v=batch4buf[offs0+j]*v0+v1-xy.Get(cstart+j,nin+i);
            batch4buf.Set(offs1+j,v*v0);
            e+=v*v/2;
           }
        }
     }
//--- Backpropagation
   for(neuronidx=ntotal-1; neuronidx>=0; neuronidx--)
     {
      entryoffs=entrysize*neuronidx;
      offs=istart+neuronidx*m_nfieldwidth;
      neurontype=network.m_structinfo[offs+0];
      if(neurontype>0 || neurontype==-5)
        {
         //--- Activation function
         srcneuronidx=network.m_structinfo[offs+2];
         srcentryoffs=entrysize*srcneuronidx;
         offs0=srcentryoffs+derroroffs;
         offs1=entryoffs+derroroffs;
         offs2=entryoffs+dfoffs;
         batch4buf.Set(offs0,batch4buf[offs0]+batch4buf[offs1]*batch4buf[offs2]);
         batch4buf.Set(offs0+1,batch4buf[offs0+1]+batch4buf[offs1+1]*batch4buf[offs2+1]);
         batch4buf.Set(offs0+2,batch4buf[offs0+2]+batch4buf[offs1+2]*batch4buf[offs2+2]);
         batch4buf.Set(offs0+3,batch4buf[offs0+3]+batch4buf[offs1+3]*batch4buf[offs2+3]);
         continue;
        }
      if(neurontype==0)
        {
         //--- Adaptive summator
         nweights=network.m_structinfo[offs+1];
         srcneuronidx=network.m_structinfo[offs+2];
         srcentryoffs=entrysize*srcneuronidx;
         srcweightidx=network.m_structinfo[offs+3];
         v0=batch4buf[entryoffs+derroroffs];
         v1=batch4buf[entryoffs+derroroffs+1];
         v2=batch4buf[entryoffs+derroroffs+2];
         v3=batch4buf[entryoffs+derroroffs+3];
         for(j=0; j<nweights; j++)
           {
            offs0=srcentryoffs;
            offs1=srcentryoffs+derroroffs;
            v=network.m_weights[srcweightidx];
            hpcbuf.Set(srcweightidx,hpcbuf[srcweightidx]+batch4buf[offs0]*v0+batch4buf[offs0+1]*v1+batch4buf[offs0+2]*v2+batch4buf[offs0+3]*v3);
            batch4buf.Set(offs1,batch4buf[offs1+0]+v*v0);
            batch4buf.Set(offs1+1,batch4buf[offs1+1]+v*v1);
            batch4buf.Set(offs1+2,batch4buf[offs1+2]+v*v2);
            batch4buf.Set(offs1+3,batch4buf[offs1+3]+v*v3);
            srcentryoffs+=entrysize;
            srcweightidx ++;
           }
         continue;
        }
      if(neurontype<0)
        {
         bflag=false;
         if((neurontype==-2 || neurontype==-3) || neurontype==-4)
           {
            //--- Special neuron type, no back-propagation required
            bflag=true;
           }
         if(!CAp::Assert(bflag,"MLPInternalCalculateGradient: unknown neuron type!"))
            return;
         continue;
        }
     }
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CMLPBase::MLPChunkedProcess(CMultilayerPerceptron &network,
                                 CMatrixDouble &xy,
                                 int cstart,int csize,
                                 CRowDouble &batch4buf,
                                 CRowDouble &hpcbuf)
  {
//--- create variables
   int    i=0;
   int    j=0;
   int    ntotal=0;
   int    nin=0;
   int    nout=0;
   int    offs=0;
   double f=0;
   double df=0;
   double d2f=0;
   double v=0;
   bool   bflag;
   int    istart=0;
   int    entrysize=0;
   int    entryoffs=0;
   int    neuronidx=0;
   int    srcentryoffs=0;
   int    srcneuronidx=0;
   int    srcweightidx=0;
   int    neurontype=0;
   int    nweights=0;
   int    offs0=0;
   double v0=0;
   double v1=0;
   double v2=0;
   double v3=0;
   double s0=0;
   double s1=0;
   double s2=0;
   double s3=0;
   int    chunksize= 4;
//--- check
   if(!CAp::Assert(csize<=chunksize,__FUNCTION__": internal error (CSize>ChunkSize)"))
      return;
//--- Read network geometry, prepare data
   nin=network.m_structinfo[1];
   nout=network.m_structinfo[2];
   ntotal=network.m_structinfo[3];
   istart=network.m_structinfo[5];
   entrysize=4;
//--- Fill Batch4Buf by zeros.
//--- THIS STAGE IS VERY IMPORTANT!
//--- We fill all components of entry - neuron values, dF/dNET, dError/dF.
//--- It allows us to easily handle  situations  when  CSize<ChunkSize  by
//--- simply  working  with  ALL  components  of  Batch4Buf,  without ever
//--- looking at CSize.
   for(i=0; i<entrysize*ntotal; i++)
      batch4buf.Fill(0);
//--- Forward pass:
//--- 1. Load data into Batch4Buf. If CSize<ChunkSize, data are padded by zeros.
//--- 2. Perform forward pass through network
   for(i=0; i<nin; i++)
     {
      entryoffs=entrysize*i;
      for(j=0; j<csize; j++)
        {
         if((double)(network.m_columnsigmas[i])!=0.0)
            batch4buf.Set(entryoffs+j,(xy.Get(cstart+j,i)-network.m_columnmeans[i])/network.m_columnsigmas[i]);
         else
            batch4buf.Set(entryoffs+j,xy.Get(cstart+j,i)-network.m_columnmeans[i]);
        }
     }
   for(neuronidx=0; neuronidx<ntotal; neuronidx++)
     {
      entryoffs=entrysize*neuronidx;
      offs=istart+neuronidx*m_nfieldwidth;
      neurontype=network.m_structinfo[offs+0];
      if(neurontype>0 || neurontype==-5)
        {
         //--- "activation function" neuron, which takes value of neuron SrcNeuronIdx
         //--- and applies activation function to it.
         //--- This neuron has no weights and no tunable parameters.
         srcneuronidx=network.m_structinfo[offs+2];
         srcentryoffs=entrysize*srcneuronidx;
         for(i=0; i<4; i++)
           {
            MLPActivationFunction(batch4buf[srcentryoffs+i],neurontype,f,df,d2f);
            batch4buf.Set(entryoffs+i,f);
           }
         continue;
        }
      if(neurontype==0)
        {
         //--- "adaptive summator" neuron, whose output is a weighted sum of inputs.
         //--- It has weights, but has no activation function.
         nweights=network.m_structinfo[offs+1];
         srcneuronidx=network.m_structinfo[offs+2];
         srcentryoffs=entrysize*srcneuronidx;
         srcweightidx=network.m_structinfo[offs+3];
         v0=0;
         v1=0;
         v2=0;
         v3=0;
         for(j=0; j<nweights; j++)
           {
            v=network.m_weights[srcweightidx];
            srcweightidx++;
            v0+=v*batch4buf[srcentryoffs];
            v1+=v*batch4buf[srcentryoffs+1];
            v2+=v*batch4buf[srcentryoffs+2];
            v3+=v*batch4buf[srcentryoffs+3];
            srcentryoffs+=entrysize;
           }
         batch4buf.Set(entryoffs,v0);
         batch4buf.Set(entryoffs+1,v1);
         batch4buf.Set(entryoffs+2,v2);
         batch4buf.Set(entryoffs+3,v3);
         continue;
        }
      if(neurontype<0)
        {
         bflag=false;
         switch(neurontype)
           {
            case -2:
               //--- Input neuron, left unchanged
               bflag=true;
               break;
            case -3:
               //--- "-1" neuron
               batch4buf.Set(entryoffs,-1);
               batch4buf.Set(entryoffs+1,-1);
               batch4buf.Set(entryoffs+2,-1);
               batch4buf.Set(entryoffs+3,-1);
               bflag=true;
               break;
            case -4:
               //--- "0" neuron
               batch4buf.Set(entryoffs,0);
               batch4buf.Set(entryoffs+1,0);
               batch4buf.Set(entryoffs+2,0);
               batch4buf.Set(entryoffs+3,0);
               bflag=true;
               break;
           }
         if(!CAp::Assert(bflag,__FUNCTION__": internal error - unknown neuron type!"))
            return;
         continue;
        }
     }
//--- SOFTMAX normalization or scaling.
   if(!CAp::Assert(network.m_structinfo[6]==0 || network.m_structinfo[6]==1,__FUNCTION__": unknown normalization type!"))
      return;
   if(network.m_structinfo[6]==1)
     {
      //--- SOFTMAX-normalized network.
      //--- First,  calculate (V0,V1,V2,V3)  -  component-wise  maximum
      //--- of output neurons. This vector of maximum  values  will  be
      //--- used for normalization  of  outputs  prior  to  calculating
      //--- exponentials.
      //--- NOTE: the only purpose of this stage is to prevent overflow
      //---       during calculation of exponentials.  With  this stage
      //---       we  make  sure  that  all exponentials are calculated
      //---       with non-positive argument. If you load (0,0,0,0)  to
      //---       (V0,V1,V2,V3), your program will continue  working  -
      //---       although with less robustness.
      entryoffs=entrysize*(ntotal-nout);
      v0=batch4buf[entryoffs+0];
      v1=batch4buf[entryoffs+1];
      v2=batch4buf[entryoffs+2];
      v3=batch4buf[entryoffs+3];
      entryoffs+=entrysize;
      for(i=1; i<nout; i++)
        {
         v=batch4buf[entryoffs];
         if(v>v0)
            v0=v;
         v=batch4buf[entryoffs+1];
         if(v>v1)
            v1=v;
         v=batch4buf[entryoffs+2];
         if(v>v2)
            v2=v;
         v=batch4buf[entryoffs+3];
         if(v>v3)
            v3=v;
         entryoffs+=entrysize;
        }
      //--- Then,  calculate exponentials and place them to part of the
      //--- array which  is  located  past  the  last  entry.  We  also
      //--- calculate sum of exponentials.
      entryoffs=entrysize*(ntotal-nout);
      offs0=entrysize*ntotal;
      s0=0;
      s1=0;
      s2=0;
      s3=0;
      for(i=0; i<nout; i++)
        {
         v=MathExp(batch4buf[entryoffs]-v0);
         s0+=v;
         batch4buf.Set(offs0,v);
         v=MathExp(batch4buf[entryoffs+1]-v1);
         s1+=v;
         batch4buf.Set(offs0+1,v);
         v=MathExp(batch4buf[entryoffs+2]-v2);
         s2+=v;
         batch4buf.Set(offs0+2,v);
         v=MathExp(batch4buf[entryoffs+3]-v3);
         s3+=v;
         batch4buf.Set(offs0+3,v);
         entryoffs+=entrysize;
         offs0+=chunksize;
        }
      //--- Write SOFTMAX-normalized values to the output array.
      offs0=entrysize*ntotal;
      for(i=0; i<nout; i++)
        {
         if(csize>0)
            xy.Set(cstart,nin+i,batch4buf[offs0]/s0);
         if(csize>1)
            xy.Set(cstart+1,nin+i,batch4buf[offs0+1]/s1);
         if(csize>2)
            xy.Set(cstart+2,nin+i,batch4buf[offs0+2]/s2);
         if(csize>3)
            xy.Set(cstart+3,nin+i,batch4buf[offs0+3]/s3);
         offs0+=chunksize;
        }
     }
   else
     {
      //--- Regression network with sum-of-squares function.
      //--- For each NOut of last neurons:
      //--- * calculate difference between actual and desired output
      //--- * calculate dError/dOut for this neuron (proportional to difference)
      //--- * store in in last 4 components of entry (these values are used
      //---   to start backpropagation)
      //--- * update error
      for(i=0; i<nout; i++)
        {
         v0=network.m_columnsigmas[nin+i];
         v1=network.m_columnmeans[nin+i];
         entryoffs=entrysize*(ntotal-nout+i);
         for(j=0; j<csize; j++)
            xy.Set(cstart+j,nin+i,batch4buf[entryoffs+j]*v0+v1);
        }
     }
  }
//+------------------------------------------------------------------+
//| Returns T*Ln(T/Z), guarded against overflow/underflow.           |
//| Internal subroutine.                                             |
//+------------------------------------------------------------------+
double CMLPBase::SafeCrossEntropy(const double t,const double z)
  {
//--- create variables
   double result=0;
   double r=0;
//--- check
   if(t==0.0)
      result=0;
   else
     {
      //--- check
      if(MathAbs(z)>1.0)
        {
         //--- Shouldn't be the case with softmax,
         //--- but we just want to be sure.
         if(t/z==0.0)
            r=CMath::m_minrealnumber;
         else
            r=t/z;
        }
      else
        {
         //--- Normal case
         if(z==0.0 || MathAbs(t)>=CMath::m_maxrealnumber*MathAbs(z))
            r=CMath::m_maxrealnumber;
         else
            r=t/z;
        }
      //--- get result
      result=t*MathLog(r);
     }
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| This function performs backward pass of neural network           |
//| randimization:                                                   |
//|   * it assumes that Network.Weights stores standard deviation of |
//|     weights (weights are not generated yet, only their deviations|
//|     are present)                                                 |
//|   * it sets deviations of weights which feed NeuronIdx-th neuron |
//|     to specified value                                           |
//|   * it recursively passes to deeper neuron and modifies their    |
//|     weights                                                      |
//|   * it stops after encountering nonlinear neurons, linear        |
//|     activation function, input neurons, "0" and "-1" neurons     |
//+------------------------------------------------------------------+
void CMLPBase::RandomizeBackwardPass(CMultilayerPerceptron &network,
                                     int neuronidx,double v)
  {
//--- create variables
   int istart=0;
   int neurontype=0;
   int n1=0;
   int n2=0;
   int w1=0;
   int w2=0;
   int offs=0;

   istart=network.m_structinfo[5];
   neurontype=network.m_structinfo[istart+neuronidx*m_nfieldwidth];
   switch(neurontype)
     {
      case -2:
         //--- Input neuron - stop
         break;
      case -3:
         //--- "-1" neuron: stop
         break;
      case -4:
         //--- "0" neuron: stop
         break;
      case 0:
         //--- Adaptive summator neuron:
         //--- * modify deviations of its weights
         //--- * recursively call this function for its inputs
         offs=istart+neuronidx*m_nfieldwidth;
         n1=network.m_structinfo[offs+2];
         n2=n1+network.m_structinfo[offs+1];
         w1=network.m_structinfo[offs+3];
         w2=w1+network.m_structinfo[offs+1];
         for(int i=w1; i<w2; i++)
            network.m_weights.Set(i,v);
         for(int i=n1; i<n2; i++)
            RandomizeBackwardPass(network,i,v);
         break;
      case -5:
         //--- Linear activation function: stop
         break;
      default:
         if(!CAp::Assert(neurontype>0,__FUNCTION__": unexpected neuron type"))
            return;
         break;
     }
  }
//+------------------------------------------------------------------+
//| Auxiliary class for CLogit                                       |
//+------------------------------------------------------------------+
class CLogitModel
  {
public:
   CRowDouble        m_w;
   //--- constructor, destructor
                     CLogitModel(void) {}
                    ~CLogitModel(void) {}
   //--- copy
   void              Copy(const CLogitModel &obj);
   //--- overloading
   void              operator=(const CLogitModel &obj) { Copy(obj); }
  };
//+------------------------------------------------------------------+
//| Copy                                                             |
//+------------------------------------------------------------------+
void CLogitModel::Copy(const CLogitModel &obj)
  {
//--- copy array
   m_w=obj.m_w;
  }
//+------------------------------------------------------------------+
//| This class is a shell for class CLogitModel                      |
//+------------------------------------------------------------------+
class CLogitModelShell
  {
private:
   CLogitModel       m_innerobj;

public:
   //--- constructors, destructor
                     CLogitModelShell(void) {}
                     CLogitModelShell(CLogitModel &obj) { m_innerobj.Copy(obj); }
                    ~CLogitModelShell(void) {}
   //--- method
   CLogitModel      *GetInnerObj(void) { return(GetPointer(m_innerobj)); }
  };
//+------------------------------------------------------------------+
//| Auxiliary class for CLogit                                       |
//+------------------------------------------------------------------+
struct CLogitMCState
  {
   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
                     CLogitMCState(void) { ZeroMemory(this); }
                    ~CLogitMCState(void) {}
   //---
   void              Copy(const CLogitMCState &obj);
   //--- overloading
   void              operator=(const CLogitMCState &obj) { Copy(obj); }
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CLogitMCState::Copy(const CLogitMCState &obj)
  {
   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;
  }
//+------------------------------------------------------------------+
//| MNLReport structure contains information about training process: |
//| * NGrad     -   number of gradient calculations                  |
//| * NHess     -   number of Hessian calculations                   |
//+------------------------------------------------------------------+
class CMNLReport
  {
public:
   //--- variables
   int               m_ngrad;
   int               m_nhess;
   //--- constructor, destructor
                     CMNLReport(void) { ZeroMemory(this); }
                    ~CMNLReport(void) {}
   //--- copy
   void              Copy(CMNLReport &obj);
  };
//+------------------------------------------------------------------+
//| Copy                                                             |
//+------------------------------------------------------------------+
void CMNLReport::Copy(CMNLReport &obj)
  {
//--- copy variables
   m_ngrad=obj.m_ngrad;
   m_nhess=obj.m_nhess;
  }
//+------------------------------------------------------------------+
//| MNLReport structure contains information about training process: |
//| * NGrad     -   number of gradient calculations                  |
//| * NHess     -   number of Hessian calculations                   |
//+------------------------------------------------------------------+
class CMNLReportShell
  {
private:
   CMNLReport        m_innerobj;

public:
   //--- constructors, destructor
                     CMNLReportShell(void) {}
                     CMNLReportShell(CMNLReport &obj) { m_innerobj.Copy(obj); }
                    ~CMNLReportShell(void) {}
   //--- methods
   int               GetNGrad(void);
   void              SetNGrad(const int i);
   int               GetNHess(void);
   void              SetNHess(const int i);
   CMNLReport       *GetInnerObj(void);
  };
//+------------------------------------------------------------------+
//| Returns the value of the variable ngrad                          |
//+------------------------------------------------------------------+
int CMNLReportShell::GetNGrad(void)
  {
   return(m_innerobj.m_ngrad);
  }
//+------------------------------------------------------------------+
//| Changing the value of the variable ngrad                         |
//+------------------------------------------------------------------+
void CMNLReportShell::SetNGrad(const int i)
  {
   m_innerobj.m_ngrad=i;
  }
//+------------------------------------------------------------------+
//| Returns the value of the variable nhess                          |
//+------------------------------------------------------------------+
int CMNLReportShell::GetNHess(void)
  {
   return(m_innerobj.m_nhess);
  }
//+------------------------------------------------------------------+
//| Changing the value of the variable nhess                         |
//+------------------------------------------------------------------+
void CMNLReportShell::SetNHess(const int i)
  {
   m_innerobj.m_nhess=i;
  }
//+------------------------------------------------------------------+
//| Return object of class                                           |
//+------------------------------------------------------------------+
CMNLReport *CMNLReportShell::GetInnerObj(void)
  {
   return(GetPointer(m_innerobj));
  }
//+------------------------------------------------------------------+
//| Class logit model                                                |
//+------------------------------------------------------------------+
class CLogit
  {
public:
   //--- variables
   static const double m_xtol;
   static const double m_ftol;
   static const double m_gtol;
   static const int  m_maxfev;
   static const double m_stpmin;
   static const double m_stpmax;
   static const int  m_logitvnum;
   //--- public methods
   static void       MNLTrainH(CMatrixDouble &xy,const int npoints,const int nvars,const int nclasses,int &info,CLogitModel &lm,CMNLReport &rep);
   static void       MNLProcess(CLogitModel &lm,double &x[],double &y[]);
   static void       MNLProcess(CLogitModel &lm,CRowDouble &x,CRowDouble &y);
   static void       MNLProcessI(CLogitModel &lm,double &x[],double &y[]);
   static void       MNLProcessI(CLogitModel &lm,CRowDouble &x,CRowDouble &y);
   static void       MNLUnpack(CLogitModel &lm,CMatrixDouble &a,int &nvars,int &nclasses);
   static void       MNLPack(CMatrixDouble &a,const int nvars,const int nclasses,CLogitModel &lm);
   static void       MNLCopy(CLogitModel &lm1,CLogitModel &lm2);
   static double     MNLAvgCE(CLogitModel &lm,CMatrixDouble &xy,const int npoints);
   static double     MNLRelClsError(CLogitModel &lm,CMatrixDouble &xy,const int npoints);
   static double     MNLRMSError(CLogitModel &lm,CMatrixDouble &xy,const int npoints);
   static double     MNLAvgError(CLogitModel &lm,CMatrixDouble &xy,const int npoints);
   static double     MNLAvgRelError(CLogitModel &lm,CMatrixDouble &xy,const int ssize);
   static int        MNLClsError(CLogitModel &lm,CMatrixDouble &xy,const int npoints);

private:
   static void       MNLIExp(CRowDouble &w,CRowDouble &x);
   static void       MNLAllErrors(CLogitModel &lm,CMatrixDouble &xy,const int npoints,double &relcls,double &avgce,double &rms,double &avg,double &avgrel);
   static void       MNLMCSrch(const int n,CRowDouble &x,double &f,CRowDouble &g,CRowDouble &s,double &stp,int &info,int &nfev,CRowDouble &wa,CLogitMCState &state,int &stage);
   static void       MNLMCStep(double &stx,double &fx,double &dx,double &sty,double &fy,double &dy,double &stp,const double fp,const double dp,bool &brackt,const double stmin,const double stmax,int &info);
  };
//+------------------------------------------------------------------+
//| Initialize constants                                             |
//+------------------------------------------------------------------+
const double CLogit::m_xtol=100*CMath::m_machineepsilon;
const double CLogit::m_ftol=0.0001;
const double CLogit::m_gtol=0.3;
const int    CLogit::m_maxfev=20;
const double CLogit::m_stpmin=1.0E-2;
const double CLogit::m_stpmax=1.0E5;
const int    CLogit::m_logitvnum=6;
//+------------------------------------------------------------------+
//| This subroutine trains logit model.                              |
//| INPUT PARAMETERS:                                                |
//|     XY          -   training set, array[0..NPoints-1,0..NVars]   |
//|                     First NVars columns store values of          |
//|                     independent variables, next column stores    |
//|                     number of class (from 0 to NClasses-1) which |
//|                     dataset element belongs to. Fractional values|
//|                     are rounded to nearest integer.              |
//|     NPoints     -   training set size, NPoints>=1                |
//|     NVars       -   number of independent variables, NVars>=1    |
//|     NClasses    -   number of classes, NClasses>=2               |
//| OUTPUT PARAMETERS:                                               |
//|     Info        -   return code:                                 |
//|                     * -2, if there is a point with class number  |
//|                           outside of [0..NClasses-1].            |
//|                     * -1, if incorrect parameters was passed     |
//|                           (NPoints<NVars+2, NVars<1, NClasses<2).|
//|                     *  1, if task has been solved                |
//|     LM          -   model built                                  |
//|     Rep         -   training report                              |
//+------------------------------------------------------------------+
void CLogit::MNLTrainH(CMatrixDouble &xy,const int npoints,
                       const int nvars,const int nclasses,
                       int &info,CLogitModel &lm,CMNLReport &rep)
  {
//--- create variables
   int    i=0;
   int    j=0;
   int    k=0;
   int    ssize=0;
   bool   allsame;
   int    offs=0;
   double threshold=0;
   double wminstep=0;
   double decay=0;
   int    wdim=0;
   int    expoffs=0;
   double v=0;
   double s=0;
   int    nin=0;
   int    nout=0;
   int    wcount=0;
   double e=0;
   bool   spd;
   double wstep=0;
   int    mcstage=0;
   int    mcinfo=0;
   int    mcnfev=0;
   int    solverinfo=0;
   int    i_=0;
   int    i1_=0;
//--- creating arrays
   CRowDouble g;
   vector<double> x;
   vector<double> y;
   vector<double> wbase;
   CRowDouble wdir;
   CRowDouble work;
//--- create matrix
   CMatrixDouble h;
//--- create objects of classes
   CLogitMCState         mcstate;
   CDenseSolverReport    solverrep;
   CMultilayerPerceptron network;
//--- initialization
   info=0;
   threshold=1000*CMath::m_machineepsilon;
   wminstep=0.001;
   decay=0.001;
//--- Test for inputs
   if((npoints<nvars+2 || nvars<1) || nclasses<2)
     {
      info=-1;
      return;
     }
   for(i=0; i<npoints; i++)
     {
      //--- check
      if((int)MathRound(xy.Get(i,nvars))<0 || (int)MathRound(xy.Get(i,nvars))>=nclasses)
        {
         info=-2;
         return;
        }
     }
//--- change value
   info=1;
//--- Initialize data
   rep.m_ngrad=0;
   rep.m_nhess=0;
//--- Allocate array
   wdim=(nvars+1)*(nclasses-1);
   offs=5;
   expoffs=offs+wdim;
   ssize=5+(nvars+1)*(nclasses-1)+nclasses;
//--- allocation
   lm.m_w.Resize(ssize);
//--- change values
   lm.m_w.Set(0,ssize);
   lm.m_w.Set(1,m_logitvnum);
   lm.m_w.Set(2,nvars);
   lm.m_w.Set(3,nclasses);
   lm.m_w.Set(4,offs);
//--- Degenerate case: all outputs are equal
   allsame=true;
   for(i=1; i<npoints; i++)
     {
      //--- check
      if((int)MathRound(xy.Get(i,nvars))!=(int)MathRound(xy.Get(i-1,nvars)))
         allsame=false;
     }
//--- check
   if(allsame)
     {
      for(i=0; i<=(nvars+1)*(nclasses-1)-1; i++)
         lm.m_w.Set(offs+i,0);
      //--- change values
      v=-(2*MathLog(CMath::m_minrealnumber));
      k=(int)MathRound(xy.Get(0,nvars));
      //--- check
      if(k==nclasses-1)
        {
         for(i=0; i<nclasses-1; i++)
            lm.m_w.Set(offs+i*(nvars+1)+nvars,-v);
        }
      else
        {
         for(i=0; i<nclasses-1; i++)
            //--- check
            if(i==k)
               lm.m_w.Set(offs+i*(nvars+1)+nvars,v);
            else
               lm.m_w.Set(offs+i*(nvars+1)+nvars,0);
        }
      //--- exit the function
      return;
     }
//--- General case.
//--- Prepare task and network. Allocate space.
   CMLPBase::MLPCreateC0(nvars,nclasses,network);
//--- function call
   CMLPBase::MLPInitPreprocessor(network,xy,npoints);
//--- function call
   CMLPBase::MLPProperties(network,nin,nout,wcount);
   for(i=0; i<=wcount-1; i++)
      network.m_weights.Set(i,(2*CMath::RandomReal()-1)/nvars);
//--- allocation
   g.Resize(wcount);
   h.Resize(wcount,wcount);
   wbase.Resize(wcount);
   wdir.Resize(wcount);
   work.Resize(wcount);
//--- First stage: optimize in gradient direction.
   for(k=0; k<=wcount/3+10; k++)
     {
      //--- Calculate gradient in starting point
      CMLPBase::MLPGradNBatch(network,xy,npoints,e,g);
      v=network.m_weights.Dot(network.m_weights);
      //--- change value
      e=e+0.5*decay*v;
      g+=network.m_weights*decay+0;
      rep.m_ngrad++;
      //--- Setup optimization scheme
      wdir=g*(-1.0)+0;
      v=wdir.Dot(wdir);
      //--- change values
      wstep=MathSqrt(v);
      wdir/=MathSqrt(v);
      mcstage=0;
      //--- function call
      MNLMCSrch(wcount,network.m_weights,e,g,wdir,wstep,mcinfo,mcnfev,work,mcstate,mcstage);
      //--- cycle
      while(mcstage!=0)
        {
         //--- function call
         CMLPBase::MLPGradNBatch(network,xy,npoints,e,g);
         v=network.m_weights.Dot(network.m_weights);
         //--- change value
         e=e+0.5*decay*v;
         g+=network.m_weights*decay+0;
         rep.m_ngrad++;
         //--- function call
         MNLMCSrch(wcount,network.m_weights,e,g,wdir,wstep,mcinfo,mcnfev,work,mcstate,mcstage);
        }
     }
//--- Second stage: use Hessian when we are close to the minimum
   while(true)
     {
      //--- Calculate and update E/G/H
      CMLPBase::MLPHessianNBatch(network,xy,npoints,e,g,h);
      v=network.m_weights.Dot(network.m_weights);
      //--- change value
      e=e+0.5*decay*v;
      g+=network.m_weights*decay+0;
      h.Diag(h.Diag(0)+decay,0);
      rep.m_nhess++;
      //--- Select step direction
      //--- NOTE: it is important to use lower-triangle Cholesky
      //--- factorization since it is much faster than higher-triangle version.
      spd=CTrFac::SPDMatrixCholesky(h,wcount,false);
      //--- function call
      CDenseSolver::SPDMatrixCholeskySolve(h,wcount,false,g,solverinfo,solverrep,wdir);
      spd=solverinfo>0;
      //--- check
      if(spd)
        {
         //--- H is positive definite.
         //--- Step in Newton direction.
         wdir*=(-1.0);
         spd=true;
        }
      else
        {
         //--- H is indefinite.
         //--- Step in gradient direction.
         wdir=g;
         wdir*=(-1.0);
         spd=false;
        }
      //--- Optimize in WDir direction
      v=wdir.Dot(wdir);
      //--- change values
      wstep=MathSqrt(v);
      wdir/=MathSqrt(v);
      mcstage=0;
      //--- function call
      MNLMCSrch(wcount,network.m_weights,e,g,wdir,wstep,mcinfo,mcnfev,work,mcstate,mcstage);
      //--- cycle
      while(mcstage!=0)
        {
         //--- function call
         CMLPBase::MLPGradNBatch(network,xy,npoints,e,g);
         v=network.m_weights.Dot(network.m_weights);
         //--- change value
         e=e+0.5*decay*v;
         g+=network.m_weights*decay+0;
         rep.m_ngrad++;
         //--- function call
         MNLMCSrch(wcount,network.m_weights,e,g,wdir,wstep,mcinfo,mcnfev,work,mcstate,mcstage);
        }
      //--- check
      if(spd && ((mcinfo==2 || mcinfo==4) || mcinfo==6))
         break;
     }
//--- Convert from NN format to MNL format
   i1_=-offs;
   for(i_=offs; i_<offs+wcount; i_++)
      lm.m_w.Set(i_,network.m_weights[i_+i1_]);
   for(k=0; k<nvars; k++)
     {
      for(i=0; i<nclasses-1; i++)
        {
         s=network.m_columnsigmas[k];
         //--- check
         if(s==0.0)
            s=1;
         //--- change values
         j=offs+(nvars+1)*i;
         v=lm.m_w[j+k];
         lm.m_w.Set(j+k,v/s);
         lm.m_w.Add(j+nvars,v*network.m_columnmeans[k]/s);
        }
     }
//--- calculation
   for(k=0; k<nclasses-1; k++)
      lm.m_w.Mul(offs+(nvars+1)*k+nvars,-1.0);
  }
//+------------------------------------------------------------------+
//| Procesing                                                        |
//| INPUT PARAMETERS:                                                |
//|     LM      -   logit model, passed by non-constant reference    |
//|                 (some fields of structure are used as temporaries|
//|                 when calculating model output).                  |
//|     X       -   input vector,  array[0..NVars-1].                |
//|     Y       -   (possibly) preallocated buffer; if size of Y is  |
//|                 less than NClasses, it will be reallocated.If it |
//|                 is large enough, it is NOT reallocated, so we    |
//|                 can save some time on reallocation.              |
//| OUTPUT PARAMETERS:                                               |
//|     Y       -   result, array[0..NClasses-1]                     |
//|                 Vector of posterior probabilities for            |
//|                 classification task.                             |
//+------------------------------------------------------------------+
void CLogit::MNLProcess(CLogitModel &lm,double &x[],double &y[])
  {
   CRowDouble X=x;
   CRowDouble Y;
   MNLProcess(lm,X,Y);
   Y.ToArray(y);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CLogit::MNLProcess(CLogitModel &lm,CRowDouble &x,CRowDouble &y)
  {
//--- create variables
   int    nvars=(int)MathRound(lm.m_w[2]);
   int    nclasses=(int)MathRound(lm.m_w[3]);
   int    offs=(int)MathRound(lm.m_w[4]);
   int    i1=0;
   double s=0;
//--- check
   if(!CAp::Assert(lm.m_w[1]==m_logitvnum,__FUNCTION__+": unexpected model version"))
      return;
//--- function call
   MNLIExp(lm.m_w,x);
   s=0;
//--- calculation
   i1=offs+(nvars+1)*(nclasses-1);
   for(int i=i1; i<(i1+nclasses); i++)
      s+=lm.m_w[i];
//--- check
   if(CAp::Len(y)<nclasses)
      y.Resize(nclasses);
//--- change values
   for(int i=0; i<=nclasses-1; i++)
      y.Set(i,lm.m_w[i1+i]/s);
  }
//+------------------------------------------------------------------+
//| 'interactive' variant of MNLProcess for languages like Python    |
//| which support constructs like "Y=MNLProcess(LM,X)" and           |
//| interactive mode of the interpreter                              |
//| This function allocates new array on each call, so it is         |
//| significantly slower than its 'non-interactive' counterpart,     |
//| but it is  more  convenient when you call it from command line.  |
//+------------------------------------------------------------------+
void CLogit::MNLProcessI(CLogitModel &lm,double &x[],double &y[])
  {
   MNLProcess(lm,x,y);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CLogit::MNLProcessI(CLogitModel &lm,CRowDouble &x,CRowDouble &y)
  {
   MNLProcess(lm,x,y);
  }
//+------------------------------------------------------------------+
//| Unpacks coefficients of logit model. Logit model have form:      |
//|     P(class=i) = S(i) / (S(0) + S(1) + ... +S(M-1))              |
//|     S(i) = Exp(A[i,0]*X[0] + ... + A[i,N-1]*X[N-1] + A[i,N]),    |
//|            when i<M-1                                            |
//|     S(M-1) = 1                                                   |
//| INPUT PARAMETERS:                                                |
//|     LM          -   logit model in ALGLIB format                 |
//| OUTPUT PARAMETERS:                                               |
//|     V           -   coefficients, array[0..NClasses-2,0..NVars]  |
//|     NVars       -   number of independent variables              |
//|     NClasses    -   number of classes                            |
//+------------------------------------------------------------------+
void CLogit::MNLUnpack(CLogitModel &lm,CMatrixDouble &a,int &nvars,
                       int &nclasses)
  {
//--- create variables
   int offs=0;
   int i1_=0;
//--- initialization
   nvars=0;
   nclasses=0;
//--- check
   if(!CAp::Assert(lm.m_w[1]==m_logitvnum,__FUNCTION__+": unexpected model version"))
      return;
//--- initialization
   nvars=(int)MathRound(lm.m_w[2]);
   nclasses=(int)MathRound(lm.m_w[3]);
   offs=(int)MathRound(lm.m_w[4]);
//--- allocation
   a.Resize(nclasses-1,nvars+1);
//--- calculation
   for(int i=0; i<=nclasses-2; i++)
     {
      i1_=offs+i*(nvars+1);
      for(int i_=0; i_<=nvars; i_++)
         a.Set(i,i_,lm.m_w[i_+i1_]);
     }
  }
//+------------------------------------------------------------------+
//| "Packs" coefficients and creates logit model in ALGLIB format    |
//| (MNLUnpack reversed).                                            |
//| INPUT PARAMETERS:                                                |
//|     A           -   model (see MNLUnpack)                        |
//|     NVars       -   number of independent variables              |
//|     NClasses    -   number of classes                            |
//| OUTPUT PARAMETERS:                                               |
//|     LM          -   logit model.                                 |
//+------------------------------------------------------------------+
void CLogit::MNLPack(CMatrixDouble &a,const int nvars,const int nclasses,
                     CLogitModel &lm)
  {
//--- create variables
   int offs=5;
   int wdim=(nvars+1)*(nclasses-1);
   int ssize=5+(nvars+1)*(nclasses-1)+nclasses;
   int i1_=0;
//--- allocation
   lm.m_w.Resize(ssize);
//--- initialization
   lm.m_w.Set(0,ssize);
   lm.m_w.Set(1,m_logitvnum);
   lm.m_w.Set(2,nvars);
   lm.m_w.Set(3,nclasses);
   lm.m_w.Set(4,offs);
//--- calculation
   for(int i=0; i<nclasses-1; i++)
     {
      i1_=-(offs+i*(nvars+1));
      for(int i_=offs+i*(nvars+1); i_<=offs+i*(nvars+1)+nvars; i_++)
         lm.m_w.Set(i_,a.Get(i,i_+i1_));
     }
  }
//+------------------------------------------------------------------+
//| Copying of LogitModel strucure                                   |
//| INPUT PARAMETERS:                                                |
//|     LM1 -   original                                             |
//| OUTPUT PARAMETERS:                                               |
//|     LM2 -   copy                                                 |
//+------------------------------------------------------------------+
void CLogit::MNLCopy(CLogitModel &lm1,CLogitModel &lm2)
  {
//--- initialization
   int k=(int)MathRound(lm1.m_w[0]);
//--- allocation
   lm2.m_w.Resize(k);
//--- copy
   lm2.m_w=lm1.m_w;
  }
//+------------------------------------------------------------------+
//| Average cross-entropy (in bits per element) on the test set      |
//| INPUT PARAMETERS:                                                |
//|     LM      -   logit model                                      |
//|     XY      -   test set                                         |
//|     NPoints -   test set size                                    |
//| RESULT:                                                          |
//|     CrossEntropy/(NPoints*ln(2)).                                |
//+------------------------------------------------------------------+
double CLogit::MNLAvgCE(CLogitModel &lm,CMatrixDouble &xy,const int npoints)
  {
//--- create variables
   double result=0;
   int    nvars=(int)MathRound(lm.m_w[2]);
   int    nclasses=(int)MathRound(lm.m_w[3]);
//--- creating arrays
   CRowDouble workx;
   CRowDouble worky;
//--- check
   if(!CAp::Assert(lm.m_w[1]==m_logitvnum,__FUNCTION__+": unexpected model version"))
      return(EMPTY_VALUE);
//--- allocation
   worky.Resize(nclasses);
//--- calculation
   for(int i=0; i<npoints; i++)
     {
      //--- check
      if(!CAp::Assert((int)MathRound(xy[i][nvars])>=0 && (int)MathRound(xy[i][nvars])<nclasses,__FUNCTION__+": incorrect class number!"))
         return(EMPTY_VALUE);
      //--- Process
      workx=xy[i]+0;
      //--- function call
      MNLProcess(lm,workx,worky);
      //--- check
      if(worky[(int)MathRound(xy.Get(i,nvars))]>0.0)
         result-=MathLog(worky[(int)MathRound(xy.Get(i,nvars))]);
      else
         result-=MathLog(CMath::m_minrealnumber);
     }
//--- return result
   return(result/(npoints*MathLog(2)));
  }
//+------------------------------------------------------------------+
//| Relative classification error on the test set                    |
//| INPUT PARAMETERS:                                                |
//|     LM      -   logit model                                      |
//|     XY      -   test set                                         |
//|     NPoints -   test set size                                    |
//| RESULT:                                                          |
//|     percent of incorrectly classified cases.                     |
//+------------------------------------------------------------------+
double CLogit::MNLRelClsError(CLogitModel &lm,CMatrixDouble &xy,
                              const int npoints)
  {
   return((double)MNLClsError(lm,xy,npoints)/(double)npoints);
  }
//+------------------------------------------------------------------+
//| RMS error on the test set                                        |
//| INPUT PARAMETERS:                                                |
//|     LM      -   logit model                                      |
//|     XY      -   test set                                         |
//|     NPoints -   test set size                                    |
//| RESULT:                                                          |
//|     root mean square error (error when estimating posterior      |
//|     probabilities).                                              |
//+------------------------------------------------------------------+
double CLogit::MNLRMSError(CLogitModel &lm,CMatrixDouble &xy,
                           const int npoints)
  {
//--- create variables
   double relcls=0;
   double avgce=0;
   double rms=0;
   double avg=0;
   double avgrel=0;
//--- check
   if(!CAp::Assert((int)MathRound(lm.m_w[1])==m_logitvnum,__FUNCTION__+": Incorrect MNL version!"))
      return(EMPTY_VALUE);
//--- function call
   MNLAllErrors(lm,xy,npoints,relcls,avgce,rms,avg,avgrel);
//--- return result
   return(rms);
  }
//+------------------------------------------------------------------+
//| Average error on the test set                                    |
//| INPUT PARAMETERS:                                                |
//|     LM      -   logit model                                      |
//|     XY      -   test set                                         |
//|     NPoints -   test set size                                    |
//| RESULT:                                                          |
//|     average error (error when estimating posterior               |
//|     probabilities).                                              |
//+------------------------------------------------------------------+
double CLogit::MNLAvgError(CLogitModel &lm,CMatrixDouble &xy,
                           const int npoints)
  {
//--- create variables
   double relcls=0;
   double avgce=0;
   double rms=0;
   double avg=0;
   double avgrel=0;
//--- check
   if(!CAp::Assert((int)MathRound(lm.m_w[1])==m_logitvnum,__FUNCTION__+": Incorrect MNL version!"))
      return(EMPTY_VALUE);
//--- function call
   MNLAllErrors(lm,xy,npoints,relcls,avgce,rms,avg,avgrel);
//--- return result
   return(avg);
  }
//+------------------------------------------------------------------+
//| Average relative error on the test set                           |
//| INPUT PARAMETERS:                                                |
//|     LM      -   logit model                                      |
//|     XY      -   test set                                         |
//|     NPoints -   test set size                                    |
//| RESULT:                                                          |
//|     average relative error (error when estimating posterior      |
//|     probabilities).                                              |
//+------------------------------------------------------------------+
double CLogit::MNLAvgRelError(CLogitModel &lm,CMatrixDouble &xy,
                              const int ssize)
  {
//--- create variables
   double relcls=0;
   double avgce=0;
   double rms=0;
   double avg=0;
   double avgrel=0;
//--- check
   if(!CAp::Assert((int)MathRound(lm.m_w[1])==m_logitvnum,__FUNCTION__+": Incorrect MNL version!"))
      return(EMPTY_VALUE);
//--- function call
   MNLAllErrors(lm,xy,ssize,relcls,avgce,rms,avg,avgrel);
//--- return result
   return(avgrel);
  }
//+------------------------------------------------------------------+
//| Classification error on test set = MNLRelClsError*NPoints        |
//+------------------------------------------------------------------+
int CLogit::MNLClsError(CLogitModel &lm,CMatrixDouble &xy,const int npoints)
  {
//--- create variables
   int result=0;
   int nvars=(int)MathRound(lm.m_w[2]);
   int nclasses=(int)MathRound(lm.m_w[3]);
   int nmax=0;
//--- creating arrays
   CRowDouble workx;
   CRowDouble worky;
//--- check
   if(!CAp::Assert(lm.m_w[1]==m_logitvnum,__FUNCTION__+": unexpected model version"))
      return(-1);
//--- allocation
   worky.Resize(nclasses);
//--- calculation
   for(int i=0; i<npoints; i++)
     {
      //--- Process
      workx=xy[i]+0;
      //--- function call
      MNLProcess(lm,workx,worky);
      //--- Logit version of the answer
      nmax=0;
      for(int j=0; j<nclasses; j++)
        {
         //--- check
         if(worky[j]>worky[nmax])
            nmax=j;
        }
      //--- compare
      if(nmax!=(int)MathRound(xy[i][nvars]))
         result++;
     }
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| Internal subroutine. Places exponents of the anti-overflow       |
//| shifted internal linear outputs into the service part of the W   |
//| array.                                                           |
//+------------------------------------------------------------------+
void CLogit::MNLIExp(CRowDouble &w,CRowDouble &x)
  {
//--- create variables
   int    nvars=(int)MathRound(w[2]);
   int    nclasses=(int)MathRound(w[3]);
   int    offs=(int)MathRound(w[4]);
   int    i1=0;
   double v=0;
   double mx=0;
   int    i1_=0;
//--- check
   if(!CAp::Assert(w[1]==m_logitvnum,__FUNCTION__+": unexpected model version"))
      return;
//--- calculation
   i1=offs+(nvars+1)*(nclasses-1);
   for(int i=0; i<nclasses-1; i++)
     {
      //--- change values
      i1_=-(offs+i*(nvars+1));
      v=0.0;
      for(int i_=offs+i*(nvars+1); i_<=offs+i*(nvars+1)+nvars-1; i_++)
         v+=w[i_]*x[i_+i1_];
      w.Set(i1+i,v+w[offs+i*(nvars+1)+nvars]);
     }
//--- change values
   w.Set(i1+nclasses-1,0);
   mx=0;
//--- calculation
   for(int i=i1; i<i1+nclasses; i++)
      mx=MathMax(mx,w[i]);
   for(int i=i1; i<i1+nclasses; i++)
      w.Set(i,MathExp(w[i]-mx));
  }
//+------------------------------------------------------------------+
//| Calculation of all types of errors                               |
//+------------------------------------------------------------------+
void CLogit::MNLAllErrors(CLogitModel &lm,CMatrixDouble &xy,
                          const int npoints,double &relcls,
                          double &avgce,double &rms,double &avg,
                          double &avgrel)
  {
//--- create variables
   int nvars=(int)MathRound(lm.m_w[2]);
   int nclasses=(int)MathRound(lm.m_w[3]);
//--- creating arrays
   CRowDouble buf;
   CRowDouble workx;
   CRowDouble y;
   CRowDouble dy;
//--- initialization
   relcls=0;
   avgce=0;
   rms=0;
   avg=0;
   avgrel=0;
//--- check
   if(!CAp::Assert((int)MathRound(lm.m_w[1])==m_logitvnum,__FUNCTION__+": Incorrect MNL version!"))
      return;
//--- allocation
   workx.Resize(nvars);
   y.Resize(nclasses);
   dy.Resize(1);
//--- function call
   CBdSS::DSErrAllocate(nclasses,buf);
   for(int i=0; i<npoints; i++)
     {
      workx=xy[i]+0;
      //--- function call
      MNLProcess(lm,workx,y);
      //--- change value
      dy.Set(0,xy.Get(i,nvars));
      //--- function call
      CBdSS::DSErrAccumulate(buf,y,dy);
     }
//--- function call
   CBdSS::DSErrFinish(buf);
//--- change values
   relcls=buf[0];
   avgce=buf[1];
   rms=buf[2];
   avg=buf[3];
   avgrel=buf[4];
  }
//+------------------------------------------------------------------+
//| 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.                               |
//| Parameters descriprion                                           |
//| 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 CLogit::MNLMCSrch(const int n,CRowDouble &x,double &f,CRowDouble &g,
                       CRowDouble &s,double &stp,int &info,int &nfev,
                       CRowDouble &wa,CLogitMCState &state,int &stage)
  {
//--- create variables
   double v=0;
   double p5=0;
   double p66=0;
   double zero=0;
//--- init
   p5=0.5;
   p66=0.66;
   state.m_xtrapf=4.0;
   zero=0;
//--- Main cycle
   while(true)
     {
      //--- check
      switch(stage)
        {
         case 0:
            //--- NEXT
            stage=2;
            break;
         //--- check
         case 2:
            state.m_infoc=1;
            info=0;
            //--- CHECK THE INPUT PARAMETERS FOR ERRORS.
            if(n<=0 || stp<=0.0 || m_ftol<0.0 || m_gtol<zero || m_xtol<zero || m_stpmin<zero || m_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=g.Dot(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=m_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;
            break;
         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>m_stpmax)
               stp=m_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.
            x=wa+s*stp+0;
            //--- next
            stage=4;
            return;
         case 4:
            info=0;
            nfev++;
            //--- calculation
            v=g.Dot(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==m_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)<=-(m_gtol*state.m_dginit))
               info=1;
            //--- check for termination.
            if(info!=0)
              {
               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,m_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.
               MNLMCStep(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.
               MNLMCStep(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;
            break;
        }
     }
  }
//+------------------------------------------------------------------+
//| Auxiliary function for MNLMCSrch                                 |
//+------------------------------------------------------------------+
void CLogit::MNLMCStep(double &stx,double &fx,double &dx,double &sty,
                       double &fy,double &dy,double &stp,const double fp,
                       const double dp,bool &brackt,const double stmin,
                       const 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(((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)
     {
      //--- change value
      info=1;
      bound=true;
      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;
      //--- change value
      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;
      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;
         //--- change values
         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;
         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 && 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(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(brackt)
              {
               //--- change values
               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;
               //--- change values
               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)
     {
      sty=stp;
      fy=fp;
      dy=dp;
     }
   else
     {
      //--- check
      if(sgnd<0.0)
        {
         sty=stx;
         fy=fx;
         dy=dx;
        }
      //--- change values
      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(brackt && bound)
     {
      //--- check
      if(sty>stx)
         stp=MathMin(stx+0.66*(sty-stx),stp);
      else
         stp=MathMax(stx+0.66*(sty-stx),stp);
     }
  }
//+------------------------------------------------------------------+
//| This structure is a MCPD (Markov Chains for Population Data)     |
//| solver. You should use ALGLIB functions in order to work with    |
//| this object.                                                     |
//+------------------------------------------------------------------+
class CMCPDState
  {
public:
   //--- variables
   int               m_n;
   int               m_npairs;
   int               m_ccnt;
   double            m_regterm;
   int               m_repinneriterationscount;
   int               m_repouteriterationscount;
   int               m_repnfev;
   int               m_repterminationtype;
   CMinBLEICState    m_bs;
   CMinBLEICReport   m_br;
   //--- arrays
   CRowInt           m_states;
   CRowInt           m_ct;
   CRowDouble        m_pw;
   CRowDouble        m_tmpp;
   CRowDouble        m_effectivew;
   CRowDouble        m_effectivebndl;
   CRowDouble        m_effectivebndu;
   CRowInt           m_effectivect;
   CRowDouble        m_h;
   //--- matrices
   CMatrixDouble     m_data;
   CMatrixDouble     m_ec;
   CMatrixDouble     m_bndl;
   CMatrixDouble     m_bndu;
   CMatrixDouble     m_c;
   CMatrixDouble     m_priorp;
   CMatrixDouble     m_effectivec;
   CMatrixDouble     m_p;
   //--- constructor, destructor
                     CMCPDState(void);
                    ~CMCPDState(void) {}
   //--- copy
   void              Copy(const CMCPDState &obj);
   //--- overloading
   void              operator=(const CMCPDState &obj) { Copy(obj); }

  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CMCPDState::CMCPDState(void)
  {
//--- copy variables
   m_n=0;
   m_npairs=0;
   m_ccnt=0;
   m_regterm=0;
   m_repinneriterationscount=0;
   m_repouteriterationscount=0;
   m_repnfev=0;
   m_repterminationtype=0;
  }
//+------------------------------------------------------------------+
//| Copy                                                             |
//+------------------------------------------------------------------+
void CMCPDState::Copy(const CMCPDState &obj)
  {
//--- copy variables
   m_n=obj.m_n;
   m_npairs=obj.m_npairs;
   m_ccnt=obj.m_ccnt;
   m_regterm=obj.m_regterm;
   m_repinneriterationscount=obj.m_repinneriterationscount;
   m_repouteriterationscount=obj.m_repouteriterationscount;
   m_repnfev=obj.m_repnfev;
   m_repterminationtype=obj.m_repterminationtype;
   m_bs.Copy(obj.m_bs);
   m_br.Copy(obj.m_br);
//--- copy arrays
   m_states=obj.m_states;
   m_ct=obj.m_ct;
   m_pw=obj.m_pw;
   m_tmpp=obj.m_tmpp;
   m_effectivew=obj.m_effectivew;
   m_effectivebndl=obj.m_effectivebndl;
   m_effectivebndu=obj.m_effectivebndu;
   m_effectivect=obj.m_effectivect;
   m_h=obj.m_h;
//--- copy matrices
   m_data=obj.m_data;
   m_ec=obj.m_ec;
   m_bndl=obj.m_bndl;
   m_bndu=obj.m_bndu;
   m_c=obj.m_c;
   m_priorp=obj.m_priorp;
   m_effectivec=obj.m_effectivec;
   m_p=obj.m_p;
  }
//+------------------------------------------------------------------+
//| This structure is a MCPD (Markov Chains for Population Data)     |
//| solver.                                                          |
//| You should use ALGLIB functions in order to work with this object|
//+------------------------------------------------------------------+
class CMCPDStateShell
  {
private:
   CMCPDState        m_innerobj;

public:
   //--- constructors, destructor
                     CMCPDStateShell(void) {}
                     CMCPDStateShell(CMCPDState &obj) { m_innerobj.Copy(obj); }
                    ~CMCPDStateShell(void) {}
   //--- method
   CMCPDState       *GetInnerObj(void) { return(GetPointer(m_innerobj)); }
  };
//+------------------------------------------------------------------+
//| This structure is a MCPD training report:                        |
//|     InnerIterationsCount    -   number of inner iterations of the|
//|                                 underlying optimization algorithm|
//|     OuterIterationsCount    -   number of outer iterations of the|
//|                                 underlying optimization algorithm|
//|     NFEV                    -   number of merit function         |
//|                                 evaluations                      |
//|     TerminationType         -   termination type                 |
//|                                 (same as for MinBLEIC optimizer, |
//|                                 positive values denote success,  |
//|                                 negative ones - failure)         |
//+------------------------------------------------------------------+
class CMCPDReport
  {
public:
   //--- variables
   int               m_inneriterationscount;
   int               m_outeriterationscount;
   int               m_nfev;
   int               m_terminationtype;
   //--- constructor, destructor
                     CMCPDReport(void) { ZeroMemory(this); }
                    ~CMCPDReport(void) {}
   //--- copy
   void              Copy(const CMCPDReport &obj);
   //--- overloading
   void              operator=(const CMCPDReport &obj) { Copy(obj); }
  };
//+------------------------------------------------------------------+
//| Copy                                                             |
//+------------------------------------------------------------------+
void CMCPDReport::Copy(const CMCPDReport &obj)
  {
//--- copy variables
   m_inneriterationscount=obj.m_inneriterationscount;
   m_outeriterationscount=obj.m_outeriterationscount;
   m_nfev=obj.m_nfev;
   m_terminationtype=obj.m_terminationtype;
  }
//+------------------------------------------------------------------+
//| This structure is a MCPD training report:                        |
//|     InnerIterationsCount    -   number of inner iterations of the|
//|                                 underlying optimization algorithm|
//|     OuterIterationsCount    -   number of outer iterations of the|
//|                                 underlying optimization algorithm|
//|     NFEV                    -   number of merit function         |
//|                                 evaluations                      |
//|     TerminationType         -   termination type                 |
//|                                 (same as for MinBLEIC optimizer, |
//|                                 positive values denote success,  |
//|                                 negative ones - failure)         |
//+------------------------------------------------------------------+
class CMCPDReportShell
  {
private:
   CMCPDReport       m_innerobj;

public:
   //--- constructors, destructor
                     CMCPDReportShell(void) {}
                     CMCPDReportShell(CMCPDReport &obj) { m_innerobj.Copy(obj); }
                    ~CMCPDReportShell(void) {}
   //--- methods
   int               GetInnerIterationsCount(void);
   void              SetInnerIterationsCount(const int i);
   int               GetOuterIterationsCount(void);
   void              SetOuterIterationsCount(const int i);
   int               GetNFev(void);
   void              SetNFev(const int i);
   int               GetTerminationType(void);
   void              SetTerminationType(const int i);
   CMCPDReport      *GetInnerObj(void);
  };
//+------------------------------------------------------------------+
//| Returns the value of the variable inneriterationscount           |
//+------------------------------------------------------------------+
int CMCPDReportShell::GetInnerIterationsCount(void)
  {
   return(m_innerobj.m_inneriterationscount);
  }
//+------------------------------------------------------------------+
//| Changing the value of the variable inneriterationscount          |
//+------------------------------------------------------------------+
void CMCPDReportShell::SetInnerIterationsCount(const int i)
  {
   m_innerobj.m_inneriterationscount=i;
  }
//+------------------------------------------------------------------+
//| Returns the value of the variable outeriterationscount           |
//+------------------------------------------------------------------+
int CMCPDReportShell::GetOuterIterationsCount(void)
  {
   return(m_innerobj.m_outeriterationscount);
  }
//+------------------------------------------------------------------+
//| Changing the value of the variable outeriterationscount          |
//+------------------------------------------------------------------+
void CMCPDReportShell::SetOuterIterationsCount(const int i)
  {
   m_innerobj.m_outeriterationscount=i;
  }
//+------------------------------------------------------------------+
//| Returns the value of the variable nfev                           |
//+------------------------------------------------------------------+
int CMCPDReportShell::GetNFev(void)
  {
   return(m_innerobj.m_nfev);
  }
//+------------------------------------------------------------------+
//| Changing the value of the variable nfev                          |
//+------------------------------------------------------------------+
void CMCPDReportShell::SetNFev(const int i)
  {
   m_innerobj.m_nfev=i;
  }
//+------------------------------------------------------------------+
//| Returns the value of the variable terminationtype                |
//+------------------------------------------------------------------+
int CMCPDReportShell::GetTerminationType(void)
  {
   return(m_innerobj.m_terminationtype);
  }
//+------------------------------------------------------------------+
//| Changing the value of the variable terminationtype               |
//+------------------------------------------------------------------+
void CMCPDReportShell::SetTerminationType(const int i)
  {
   m_innerobj.m_terminationtype=i;
  }
//+------------------------------------------------------------------+
//| Return object of class                                           |
//+------------------------------------------------------------------+
CMCPDReport *CMCPDReportShell::GetInnerObj(void)
  {
   return(GetPointer(m_innerobj));
  }
//+------------------------------------------------------------------+
//| Markov chains class                                              |
//+------------------------------------------------------------------+
class CMarkovCPD
  {
public:
   //--- constant
   static const double m_xtol;
   //--- public methods
   static void       MCPDCreate(const int n,CMCPDState &s);
   static void       MCPDCreateEntry(const int n,const int entrystate,CMCPDState &s);
   static void       MCPDCreateExit(const int n,const int exitstate,CMCPDState &s);
   static void       MCPDCreateEntryExit(const int n,const int entrystate,const int exitstate,CMCPDState &s);
   static void       MCPDAddTrack(CMCPDState &s,CMatrixDouble &xy,const int k);
   static void       MCPDSetEC(CMCPDState &s,CMatrixDouble &ec);
   static void       MCPDAddEC(CMCPDState &s,const int i,const int j,const double c);
   static void       MCPDSetBC(CMCPDState &s,CMatrixDouble &bndl,CMatrixDouble &bndu);
   static void       MCPDAddBC(CMCPDState &s,const int i,const int j,double bndl,double bndu);
   static void       MCPDSetLC(CMCPDState &s,CMatrixDouble &c,int &ct[],const int k);
   static void       MCPDSetLC(CMCPDState &s,CMatrixDouble &c,CRowInt &ct,const int k);
   static void       MCPDSetTikhonovRegularizer(CMCPDState &s,const double v);
   static void       MCPDSetPrior(CMCPDState &s,CMatrixDouble &cpp);
   static void       MCPDSetPredictionWeights(CMCPDState &s,double &pw[]);
   static void       MCPDSetPredictionWeights(CMCPDState &s,CRowDouble &pw);
   static void       MCPDSolve(CMCPDState &s);
   static void       MCPDResults(CMCPDState &s,CMatrixDouble &p,CMCPDReport &rep);

private:
   static void       MCPDInit(const int n,const int entrystate,const int exitstate,CMCPDState &s);
  };
//+------------------------------------------------------------------+
//| Initialize constant                                              |
//+------------------------------------------------------------------+
const double CMarkovCPD::m_xtol=1.0E-8;
//+------------------------------------------------------------------+
//| DESCRIPTION:                                                     |
//| This function creates MCPD (Markov Chains for Population Data)   |
//| solver.                                                          |
//| This solver can be used to find transition matrix P for          |
//| N-dimensional prediction problem where transition from X[i] to   |
//|     X[i+1] is modelled as X[i+1] = P*X[i]                        |
//| where X[i] and X[i+1] are N-dimensional population vectors       |
//| (components of each X are non-negative), and P is a N*N          |
//| transition matrix (elements of   are non-negative, each column   |
//| sums to 1.0).                                                    |
//| Such models arise when when:                                     |
//| * there is some population of individuals                        |
//| * individuals can have different states                          |
//| * individuals can transit from one state to another              |
//| * population size is constant, i.e. there is no new individuals  |
//|   and no one leaves population                                   |
//| * you want to model transitions of individuals from one state    |
//|   into another                                                   |
//| USAGE:                                                           |
//| Here we give very brief outline of the MCPD. We strongly         |
//| recommend you to read examples in the ALGLIB Reference Manual    |
//| and to read ALGLIB User Guide on data analysis which is          |
//| available at http://www.alglib.net/dataanalysis/                 |
//| 1. User initializes algorithm state with MCPDCreate() call       |
//| 2. User adds one or more tracks -  sequences of states which     |
//|    describe evolution of a system being modelled from different  |
//|    starting conditions                                           |
//| 3. User may add optional boundary, equality and/or linear        |
//|    constraints on the coefficients of P by calling one of the    |
//|    following functions:                                          |
//|    * MCPDSetEC() to set equality constraints                     |
//|    * MCPDSetBC() to set bound constraints                        |
//|    * MCPDSetLC() to set linear constraints                       |
//| 4. Optionally, user may set custom weights for prediction errors |
//|    (by default, algorithm assigns non-equal, automatically chosen|
//|    weights for errors in the prediction of different components  |
//|    of X). It can be done with a call of                          |
//|    MCPDSetPredictionWeights() function.                          |
//| 5. User calls MCPDSolve() function which takes algorithm state   |
//|    and pointer (delegate, etc.) to callback function which       |
//|    calculates F/G.                                               |
//| 6. User calls MCPDResults() to get solution                      |
//| INPUT PARAMETERS:                                                |
//|     N       -   problem dimension, N>=1                          |
//| OUTPUT PARAMETERS:                                               |
//|     State   -   structure stores algorithm state                 |
//+------------------------------------------------------------------+
void CMarkovCPD::MCPDCreate(const int n,CMCPDState &s)
  {
//--- check
   if(!CAp::Assert(n>=1,__FUNCTION__+": N<1"))
      return;
//--- function call
   MCPDInit(n,-1,-1,s);
  }
//+------------------------------------------------------------------+
//| DESCRIPTION:                                                     |
//| This function is a specialized version of MCPDCreate() function, |
//| and we recommend  you  to read comments for this function for    |
//| general information about MCPD solver.                           |
//| This function creates MCPD (Markov Chains for Population Data)   |
//| solver for "Entry-state" model, i.e. model where transition from |
//| X[i] to X[i+1] is modelled as                                    |
//|     X[i+1] = P*X[i]                                              |
//| where                                                            |
//|     X[i] and X[i+1] are N-dimensional state vectors              |
//|     P is a N*N transition matrix                                 |
//| and  one  selected component of X[] is called "entry" state and  |
//| is treated in a special way:                                     |
//|     system state always transits from "entry" state to some      |
//|     another state                                                |
//|     system state can not transit from any state into "entry"     |
//|     state                                                        |
//| Such conditions basically mean that row of P which corresponds to|
//| "entry" state is zero.                                           |
//| Such models arise when:                                          |
//| * there is some population of individuals                        |
//| * individuals can have different states                          |
//| * individuals can transit from one state to another              |
//| * population size is NOT constant -  at every moment of time     |
//|   there is some (unpredictable) amount of "new" individuals,     |
//|   which can transit into one of the states at the next turn, but |
//|   still no one leaves population                                 |
//| * you want to model transitions of individuals from one state    |
//|   into another                                                   |
//|*but you do NOT want to predict amount of "new" individuals     |
//|   because it does not depends on individuals already present     |
//|   (hence system can not transit INTO entry state - it can only   |
//|   transit FROM it).                                              |
//| This model is discussed in more details in the ALGLIB User Guide |
//| (see http://www.alglib.net/dataanalysis/ for more data).         |
//| INPUT PARAMETERS:                                                |
//|     N       -   problem dimension, N>=2                          |
//|     EntryState- index of entry state, in 0..N-1                  |
//| OUTPUT PARAMETERS:                                               |
//|     State   -   structure stores algorithm state                 |
//+------------------------------------------------------------------+
void CMarkovCPD::MCPDCreateEntry(const int n,const int entrystate,
                                 CMCPDState &s)
  {
//--- check
   if(!CAp::Assert(n>=2,__FUNCTION__+": N<2"))
      return;
//--- check
   if(!CAp::Assert(entrystate>=0,__FUNCTION__+": EntryState<0"))
      return;
//--- check
   if(!CAp::Assert(entrystate<n,__FUNCTION__+": EntryState>=N"))
      return;
//--- function call
   MCPDInit(n,entrystate,-1,s);
  }
//+------------------------------------------------------------------+
//| DESCRIPTION:                                                     |
//| This function is a specialized version of MCPDCreate() function, |
//| and we recommend  you  to read comments for this function for    |
//| general information about MCPD solver.                           |
//| This function creates MCPD (Markov Chains for Population Data)   |
//| solver for "Exit-state" model, i.e. model where transition from  |
//| X[i] to X[i+1] is modelled as                                    |
//|     X[i+1] = P*X[i]                                              |
//| where                                                            |
//|     X[i] and X[i+1] are N-dimensional state vectors              |
//|     P is a N*N transition matrix                                 |
//| and  one  selected component of X[] is called "exit" state and   |
//| is treated in a special way:                                     |
//|     system state can transit from any state into "exit" state    |
//|     system state can not transit from "exit" state into any other|
//|     state transition operator discards "exit" state (makes it    |
//|     zero at each turn)                                           |
//| Such conditions basically mean that column of P which            |
//| corresponds to "exit" state is zero. Multiplication by such P    |
//| may decrease sum of vector components.                           |
//| Such models arise when:                                          |
//| * there is some population of individuals                        |
//| * individuals can have different states                          |
//| * individuals can transit from one state to another              |
//| * population size is NOT constant - individuals can move into    |
//|   "exit" state and leave population at the next turn, but there  |
//|   are no new individuals                                         |
//| * amount of individuals which leave population can be predicted  |
//| * you want to model transitions of individuals from one state    |
//|   into another (including transitions into the "exit" state)     |
//| This model is discussed in more details in the ALGLIB User Guide |
//| (see http://www.alglib.net/dataanalysis/ for more data).         |
//| INPUT PARAMETERS:                                                |
//|     N       -   problem dimension, N>=2                          |
//|     ExitState-  index of exit state, in 0..N-1                   |
//| OUTPUT PARAMETERS:                                               |
//|     State   -   structure stores algorithm state                 |
//+------------------------------------------------------------------+
void CMarkovCPD::MCPDCreateExit(const int n,const int exitstate,
                                CMCPDState &s)
  {
//--- check
   if(!CAp::Assert(n>=2,__FUNCTION__+": N<2"))
      return;
//--- check
   if(!CAp::Assert(exitstate>=0,__FUNCTION__+": ExitState<0"))
      return;
//--- check
   if(!CAp::Assert(exitstate<n,__FUNCTION__+": ExitState>=N"))
      return;
//--- function call
   MCPDInit(n,-1,exitstate,s);
  }
//+------------------------------------------------------------------+
//| DESCRIPTION:                                                     |
//| This function is a specialized version of MCPDCreate() function, |
//| and we recommend you to read comments for this function for      |
//| general information about MCPD solver.                           |
//| This function creates MCPD (Markov Chains for Population Data)   |
//| solver for "Entry-Exit-states" model, i.e. model where transition|
//| from X[i] to X[i+1] is modelled as                               |
//|     X[i+1] = P*X[i]                                              |
//| where                                                            |
//|     X[i] and X[i+1] are N-dimensional state vectors              |
//|     P is a N*N transition matrix                                 |
//| one selected component of X[] is called "entry" state and is a   |
//| treated in special way:                                          |
//|     system state always transits from "entry" state to some      |
//|     another state                                                |
//|     system state can not transit from any state into "entry"     |
//|     state                                                        |
//| and another one component of X[] is called "exit" state and is   |
//| treated in a special way too:                                    |
//|     system state can transit from any state into "exit" state    |
//|     system state can not transit from "exit" state into any other|
//|     state transition operator discards "exit" state (makes it    |
//|     zero at each turn)                                           |
//| Such conditions basically mean that:                             |
//|     row of P which corresponds to "entry" state is zero          |
//|     column of P which corresponds to "exit" state is zero        |
//| Multiplication by such P may decrease sum of vector components.  |
//| Such models arise when:                                          |
//| * there is some population of individuals                        |
//| * individuals can have different states                          |
//| * individuals can transit from one state to another              |
//| * population size is NOT constant                                |
//| * at every moment of time there is some (unpredictable) amount   |
//|   of "new" individuals, which can transit into one of the states |
//|   at the next turn                                               |
//|*some individuals can move (predictably) into "exit" state      |
//|   and leave population at the next turn                          |
//| * you want to model transitions of individuals from one state    |
//|   into another, including transitions from the "entry" state and |
//|   into the "exit" state.                                         |
//|*but you do NOT want to predict amount of "new" individuals     |
//|   because it does not depends on individuals already present     |
//|   (hence system can not transit INTO entry state - it can only   |
//|   transit FROM it).                                              |
//| This model is discussed  in  more  details  in  the ALGLIB User  |
//| Guide (see http://www.alglib.net/dataanalysis/ for more data).   |
//| INPUT PARAMETERS:                                                |
//|     N       -   problem dimension, N>=2                          |
//|     EntryState- index of entry state, in 0..N-1                  |
//|     ExitState-  index of exit state, in 0..N-1                   |
//| OUTPUT PARAMETERS:                                               |
//|     State   -   structure stores algorithm state                 |
//+------------------------------------------------------------------+
void CMarkovCPD::MCPDCreateEntryExit(const int n,const int entrystate,
                                     const int exitstate,CMCPDState &s)
  {
//--- check
   if(!CAp::Assert(n>=2,__FUNCTION__+": N<2"))
      return;
//--- check
   if(!CAp::Assert(entrystate>=0,__FUNCTION__+": EntryState<0"))
      return;
//--- check
   if(!CAp::Assert(entrystate<n,__FUNCTION__+": EntryState>=N"))
      return;
//--- check
   if(!CAp::Assert(exitstate>=0,__FUNCTION__+": ExitState<0"))
      return;
//--- check
   if(!CAp::Assert(exitstate<n,__FUNCTION__+": ExitState>=N"))
      return;
//--- check
   if(!CAp::Assert(entrystate!=exitstate,__FUNCTION__+": EntryState=ExitState"))
      return;
//--- function call
   MCPDInit(n,entrystate,exitstate,s);
  }
//+------------------------------------------------------------------+
//| This function is used to add a track - sequence of system states |
//| at the different moments of its evolution.                       |
//| You may add one or several tracks to the MCPD solver. In case you|
//| have several tracks, they won't overwrite each other. For        |
//| example, if you pass two tracks, A1-A2-A3 (system at t=A+1, t=A+2|
//| and t=A+3) and B1-B2-B3, then solver will try to model           |
//| transitions from t=A+1 to t=A+2, t=A+2 to t=A+3, t=B+1 to t=B+2, |
//| t=B+2 to t=B+3. But it WONT mix these two tracks - i.e. it wont  |
//| try to model transition from t=A+3 to t=B+1.                     |
//| INPUT PARAMETERS:                                                |
//|     S       -   solver                                           |
//|     XY      -   track, array[K,N]:                               |
//|                 * I-th row is a state at t=I                     |
//|                 * elements of XY must be non-negative (exception |
//|                   will be thrown on negative elements)           |
//|     K       -   number of points in a track                      |
//|                 * if given, only leading K rows of XY are used   |
//|                 * if not given, automatically determined from    |
//|                   size of XY                                     |
//| NOTES:                                                           |
//| 1. Track may contain either proportional or population data:     |
//|    * with proportional data all rows of XY must sum to 1.0, i.e. |
//|      we have proportions instead of absolute population values   |
//|    * with population data rows of XY contain population counts   |
//|      and generally do not sum to 1.0 (although they still must be|
//|      non-negative)                                               |
//+------------------------------------------------------------------+
void CMarkovCPD::MCPDAddTrack(CMCPDState &s,CMatrixDouble &xy,
                              const int k)
  {
//--- create variables
   int    n=s.m_n;
   double s0=0;
   double s1=0;
//--- check
   if(!CAp::Assert(k>=0,__FUNCTION__+": K<0"))
      return;
//--- check
   if(!CAp::Assert((int)CAp::Cols(xy)>=n,__FUNCTION__+": Cols(XY)<N"))
      return;
//--- check
   if(!CAp::Assert((int)CAp::Rows(xy)>=k,__FUNCTION__+": Rows(XY)<K"))
      return;
//--- check
   if(!CAp::Assert(CApServ::IsFiniteMatrix(xy,k,n),__FUNCTION__+": XY contains infinite or NaN elements"))
      return;

   for(int i=0; i<k; i++)
     {
      for(int j=0; j<n; j++)
        {
         //--- check
         if(!CAp::Assert(xy.Get(i,j)>=0.0,__FUNCTION__+": XY contains negative elements"))
            return;
        }
     }
//--- check
   if(k<2)
      return;
//--- check
   if((int)CAp::Rows(s.m_data)<s.m_npairs+k-1)
      CApServ::RMatrixResize(s.m_data,MathMax(2*(int)CAp::Rows(s.m_data),s.m_npairs+k-1),2*n);
//--- calculation
   for(int i=0; i<k-1; i++)
     {
      s0=0;
      s1=0;
      for(int j=0; j<n; j++)
        {
         //--- check
         if(s.m_states[j]>=0)
            s0+=xy.Get(i,j);
         //--- check
         if(s.m_states[j]<=0)
            s1+=xy.Get(i+1,j);
        }
      //--- check
      if(s0>0.0 && s1>0.0)
        {
         for(int j=0; j<n; j++)
           {
            //--- check
            if(s.m_states[j]>=0)
               s.m_data.Set(s.m_npairs,j,xy.Get(i,j)/s0);
            else
               s.m_data.Set(s.m_npairs,j,0.0);
            //--- check
            if(s.m_states[j]<=0)
               s.m_data.Set(s.m_npairs,n+j,xy.Get(i+1,j)/s1);
            else
               s.m_data.Set(s.m_npairs,n+j,0.0);
           }
         //--- change value
         s.m_npairs++;
        }
     }
  }
//+------------------------------------------------------------------+
//| This function is used to add equality constraints on the elements|
//| of the transition matrix P.                                      |
//| MCPD solver has four types of constraints which can be placed    |
//| on P:                                                            |
//| * user-specified equality constraints (optional)                 |
//| * user-specified bound constraints (optional)                    |
//| * user-specified general linear constraints (optional)           |
//| * basic constraints (always present):                            |
//|   * non-negativity: P[i,j]>=0                                    |
//|   * consistency: every column of P sums to 1.0                   |
//| Final constraints which are passed to the underlying optimizer   |
//| are calculated as intersection of all present constraints. For   |
//| example, you may specify boundary constraint on P[0,0] and       |
//| equality one:                                                    |
//|     0.1<=P[0,0]<=0.9                                             |
//|     P[0,0]=0.5                                                   |
//| Such combination of constraints will be silently reduced to their|
//| intersection, which is P[0,0]=0.5.                               |
//| This function can be used to place equality constraints on       |
//| arbitrary subset of elements of P. Set of constraints is         |
//| specified by EC, which may contain either NAN's or finite numbers|
//| from [0,1]. NAN denotes absence of constraint, finite number     |
//| denotes equality constraint on specific element of P.            |
//| You can also use MCPDAddEC() function which allows to ADD        |
//| equality constraint for one element of P without changing        |
//| constraints for other elements.                                  |
//| These functions (MCPDSetEC and MCPDAddEC) interact as follows:   |
//| * there is internal matrix of equality constraints which is      |
//|   stored in the MCPD solver                                      |
//| * MCPDSetEC() replaces this matrix by another one (SET)          |
//| * MCPDAddEC() modifies one element of this matrix and leaves     |
//|   other ones unchanged (ADD)                                     |
//| * thus MCPDAddEC() call preserves all modifications done by      |
//|   previous calls, while MCPDSetEC() completely discards all      |
//|   changes done to the equality constraints.                      |
//| INPUT PARAMETERS:                                                |
//|     S       -   solver                                           |
//|     EC      -   equality constraints, array[N,N]. Elements of EC |
//|                 can be either NAN's or finite numbers from [0,1].|
//|                 NAN denotes absence of constraints, while finite |
//|                 value denotes equality constraint on the         |
//|                 corresponding element of P.                      |
//| NOTES:                                                           |
//| 1. infinite values of EC will lead to exception being thrown.    |
//| Values less than 0.0 or greater than 1.0 will lead to error code |
//| being returned after call to MCPDSolve().                        |
//+------------------------------------------------------------------+
void CMarkovCPD::MCPDSetEC(CMCPDState &s,CMatrixDouble &ec)
  {
   int n=s.m_n;
//--- check
   if(!CAp::Assert((int)CAp::Cols(ec)>=n,__FUNCTION__+": Cols(EC)<N"))
      return;
//--- check
   if(!CAp::Assert((int)CAp::Rows(ec)>=n,__FUNCTION__+": Rows(EC)<N"))
      return;
//--- calculation
   if(!CAp::Assert(CApServ::IsFiniteOrNaNMatrix(ec,n,n),"MCPDSetEC: EC containts infinite elements"))
      return;
   s.m_ec=ec;
   s.m_ec.Resize(n,n);
  }
//+------------------------------------------------------------------+
//| This function is used to add equality constraints on the elements|
//| of the transition matrix P.                                      |
//| MCPD solver has four types of constraints which can be placed    |
//| on P:                                                            |
//| * user-specified equality constraints (optional)                 |
//| * user-specified bound constraints (optional)                    |
//| * user-specified general linear constraints (optional)           |
//| * basic constraints (always present):                            |
//|   * non-negativity: P[i,j]>=0                                    |
//|   * consistency: every column of P sums to 1.0                   |
//| Final constraints which are passed to the underlying optimizer   |
//| are calculated as intersection of all present constraints. For   |
//| example, you may specify boundary constraint on P[0,0] and       |
//| equality one:                                                    |
//|     0.1<=P[0,0]<=0.9                                             |
//|     P[0,0]=0.5                                                   |
//| Such combination of constraints will be silently reduced to their|
//| intersection, which is P[0,0]=0.5.                               |
//| This function can be used to ADD equality constraint for one     |
//| element of P without changing constraints for other elements.    |
//| You can also use MCPDSetEC() function which allows you to specify|
//| arbitrary set of equality constraints in one call.               |
//| These functions (MCPDSetEC and MCPDAddEC) interact as follows:   |
//| * there is internal matrix of equality constraints which is      |
//|   stored in the MCPD solver                                      |
//| * MCPDSetEC() replaces this matrix by another one (SET)          |
//| * MCPDAddEC() modifies one element of this matrix and leaves     |
//|   other ones unchanged (ADD)                                     |
//| * thus MCPDAddEC() call preserves all modifications done by      |
//|   previous calls, while MCPDSetEC() completely discards all      |
//|   changes done to the equality constraints.                      |
//| INPUT PARAMETERS:                                                |
//|     S       -   solver                                           |
//|     I       -   row index of element being constrained           |
//|     J       -   column index of element being constrained        |
//|     C       -   value (constraint for P[I,J]). Can be either NAN |
//|                 (no constraint) or finite value from [0,1].      |
//| NOTES:                                                           |
//| 1. infinite values of C will lead to exception being thrown.     |
//| Values less than 0.0 or greater than 1.0 will lead to error code |
//| being returned after call to MCPDSolve().                        |
//+------------------------------------------------------------------+
void CMarkovCPD::MCPDAddEC(CMCPDState &s,const int i,const int j,
                           const double c)
  {
//--- check
   if(!CAp::Assert(i>=0,__FUNCTION__+": I<0"))
      return;
//--- check
   if(!CAp::Assert(i<s.m_n,__FUNCTION__+": I>=N"))
      return;
//--- check
   if(!CAp::Assert(j>=0,__FUNCTION__+": J<0"))
      return;
//--- check
   if(!CAp::Assert(j<s.m_n,__FUNCTION__+": J>=N"))
      return;
//--- check
   if(!CAp::Assert(CInfOrNaN::IsNaN(c) || MathIsValidNumber(c),"MCPDAddEC: C is not finite number or NAN"))
      return;
   s.m_ec.Set(i,j,c);
  }
//+------------------------------------------------------------------+
//| This function is used to add bound constraints on the elements   |
//| of the transition matrix P.                                      |
//| MCPD solver has four types of constraints which can be placed    |
//| on P:                                                            |
//| * user-specified equality constraints (optional)                 |
//| * user-specified bound constraints (optional)                    |
//| * user-specified general linear constraints (optional)           |
//| * basic constraints (always present):                            |
//|   * non-negativity: P[i,j]>=0                                    |
//|   * consistency: every column of P sums to 1.0                   |
//| Final constraints which are passed to the underlying optimizer   |
//| are calculated as intersection of all present constraints. For   |
//| example, you may specify boundary constraint on P[0,0] and       |
//| equality one:                                                    |
//|     0.1<=P[0,0]<=0.9                                             |
//|     P[0,0]=0.5                                                   |
//| Such combination of constraints will be silently reduced to their|
//| intersection, which is P[0,0]=0.5.                               |
//| This function can be used to place bound constraints on arbitrary|
//| subset of elements of P. Set of constraints is specified by      |
//| BndL/BndU matrices, which may contain arbitrary combination of   |
//| finite numbers or infinities (like -INF<x<=0.5 or 0.1<=x<+INF).  |
//| You can also use MCPDAddBC() function which allows to ADD bound  |
//| constraint for one element of P without changing constraints for |
//| other elements.                                                  |
//| These functions (MCPDSetBC and MCPDAddBC) interact as follows:   |
//| * there is internal matrix of bound constraints which is stored  |
//|   in the MCPD solver                                             |
//| * MCPDSetBC() replaces this matrix by another one (SET)          |
//| * MCPDAddBC() modifies one element of this matrix and leaves     |
//|   other ones unchanged (ADD)                                     |
//| * thus MCPDAddBC() call preserves all modifications done by      |
//|   previous calls, while MCPDSetBC() completely discards all      |
//|   changes done to the equality constraints.                      |
//| INPUT PARAMETERS:                                                |
//|     S       -   solver                                           |
//|     BndL    -   lower bounds constraints, array[N,N]. Elements of|
//|                 BndL can be finite numbers or -INF.              |
//|     BndU    -   upper bounds constraints, array[N,N]. Elements of|
//|                 BndU can be finite numbers or +INF.              |
//+------------------------------------------------------------------+
void CMarkovCPD::MCPDSetBC(CMCPDState &s,CMatrixDouble &bndl,
                           CMatrixDouble &bndu)
  {
   int n=s.m_n;
//--- check
   if(!CAp::Assert((int)CAp::Cols(bndl)>=n,__FUNCTION__+": Cols(BndL)<N"))
      return;
//--- check
   if(!CAp::Assert((int)CAp::Rows(bndl)>=n,__FUNCTION__+": Rows(BndL)<N"))
      return;
//--- check
   if(!CAp::Assert((int)CAp::Cols(bndu)>=n,__FUNCTION__+": Cols(BndU)<N"))
      return;
//--- check
   if(!CAp::Assert((int)CAp::Rows(bndu)>=n,__FUNCTION__+": Rows(BndU)<N"))
      return;
//--- calculation
   for(int i=0; i<n; i++)
     {
      for(int j=0; j<n; j++)
        {
         //--- check
         if(!CAp::Assert(MathIsValidNumber(bndl.Get(i,j)) || CInfOrNaN::IsNegativeInfinity(bndl.Get(i,j)),"MCPDSetBC: BndL containts NAN or +INF"))
            return;
         //--- check
         if(!CAp::Assert(MathIsValidNumber(bndu.Get(i,j)) || CInfOrNaN::IsPositiveInfinity(bndu.Get(i,j)),"MCPDSetBC: BndU containts NAN or -INF"))
            return;
         //--- change values
         s.m_bndl.Set(i,j,bndl.Get(i,j));
         s.m_bndu.Set(i,j,bndu.Get(i,j));
        }
     }
  }
//+------------------------------------------------------------------+
//| This function is used to add bound constraints on the elements   |
//| of the transition matrix P.                                      |
//| MCPD solver has four types of constraints which can be placed    |
//| on P:                                                            |
//| * user-specified equality constraints (optional)                 |
//| * user-specified bound constraints (optional)                    |
//| * user-specified general linear constraints (optional)           |
//| * basic constraints (always present):                            |
//|   * non-negativity: P[i,j]>=0                                    |
//|   * consistency: every column of P sums to 1.0                   |
//| Final constraints which are passed to the underlying optimizer   |
//| are calculated as intersection of all present constraints. For   |
//| example, you may specify boundary constraint on P[0,0] and       |
//| equality one:                                                    |
//|     0.1<=P[0,0]<=0.9                                             |
//|     P[0,0]=0.5                                                   |
//| Such combination of constraints will be silently reduced to their|
//| intersection, which is P[0,0]=0.5.                               |
//| This function can be used to ADD bound constraint for one element|
//| of P without changing constraints for other elements.            |
//| You can also use MCPDSetBC() function which allows to place bound|
//| constraints on arbitrary subset of elements of P. Set of         |
//| constraints is specified  by  BndL/BndU matrices, which may      |
//| contain arbitrary combination of finite numbers or infinities    |
//| (like -INF<x<=0.5 or 0.1<=x<+INF).                               |
//| These functions (MCPDSetBC and MCPDAddBC) interact as follows:   |
//| * there is internal matrix of bound constraints which is stored  |
//|   in the MCPD solver                                             |
//| * MCPDSetBC() replaces this matrix by another one (SET)          |
//| * MCPDAddBC() modifies one element of this matrix and leaves     |
//|   other ones unchanged (ADD)                                     |
//| * thus MCPDAddBC() call preserves all modifications done by      |
//|   previous calls, while MCPDSetBC() completely discards all      |
//|   changes done to the equality constraints.                      |
//| INPUT PARAMETERS:                                                |
//|     S       -   solver                                           |
//|     I       -   row index of element being constrained           |
//|     J       -   column index of element being constrained        |
//|     BndL    -   lower bound                                      |
//|     BndU    -   upper bound                                      |
//+------------------------------------------------------------------+
void CMarkovCPD::MCPDAddBC(CMCPDState &s,const int i,const int j,
                           double bndl,double bndu)
  {
//--- check
   if(!CAp::Assert(i>=0,__FUNCTION__+": I<0"))
      return;
//--- check
   if(!CAp::Assert(i<s.m_n,__FUNCTION__+": I>=N"))
      return;
//--- check
   if(!CAp::Assert(j>=0,__FUNCTION__+": J<0"))
      return;
//--- check
   if(!CAp::Assert(j<s.m_n,__FUNCTION__+": J>=N"))
      return;
//--- check
   if(!CAp::Assert(MathIsValidNumber(bndl) || CInfOrNaN::IsNegativeInfinity(bndl),"MCPDAddBC: BndL is NAN or +INF"))
      return;
//--- check
   if(!CAp::Assert(MathIsValidNumber(bndu) || CInfOrNaN::IsPositiveInfinity(bndu),"MCPDAddBC: BndU is NAN or -INF"))
      return;
//--- change values
   s.m_bndl.Set(i,j,bndl);
   s.m_bndu.Set(i,j,bndu);
  }
//+------------------------------------------------------------------+
//| This function is used to set linear equality/inequality          |
//| constraints on the elements of the transition matrix P.          |
//| This function can be used to set one or several general linear   |
//| constraints on the elements of P. Two types of constraints are   |
//| supported:                                                       |
//| * equality constraints                                           |
//| * inequality constraints (both less-or-equal and                 |
//|   greater-or-equal)                                              |
//| Coefficients of constraints are specified by matrix C (one of the|
//| parameters). One row of C corresponds to one constraint.         |
//| Because transition matrix P has N*N elements, we need N*N columns|
//| to store all coefficients  (they  are  stored row by row), and   |
//| one more column to store right part - hence C has N*N+1 columns. |
//| Constraint kind is stored in the CT array.                       |
//| Thus, I-th linear constraint is                                  |
//|     P[0,0]*C[I,0] + P[0,1]*C[I,1] + .. + P[0,N-1]*C[I,N-1] +     |
//|         + P[1,0]*C[I,N] + P[1,1]*C[I,N+1] + ... +                |
//|         + P[N-1,N-1]*C[I,N*N-1]  ?=?  C[I,N*N]                   |
//| where ?=? can be either "=" (CT[i]=0), "<=" (CT[i]<0) or ">="    |
//| (CT[i]>0).                                                       |
//| Your constraint may involve only some subset of P (less than N*N |
//| elements).                                                       |
//| For example it can be something like                             |
//|     P[0,0] + P[0,1] = 0.5                                        |
//| In this case you still should pass matrix  with N*N+1 columns,   |
//| but all its elements (except for C[0,0], C[0,1] and C[0,N*N-1])  |
//| will be zero.                                                    |
//| INPUT PARAMETERS:                                                |
//|     S       -   solver                                           |
//|     C       -   array[K,N*N+1] - coefficients of constraints     |
//|                 (see above for complete description)             |
//|     CT      -   array[K] - constraint types                      |
//|                 (see above for complete description)             |
//|     K       -   number of equality/inequality constraints, K>=0: |
//|                 * if given, only leading K elements of C/CT are  |
//|                   used                                           |
//|                 * if not given, automatically determined from    |
//|                   sizes of C/CT                                  |
//+------------------------------------------------------------------+
void CMarkovCPD::MCPDSetLC(CMCPDState &s,CMatrixDouble &c,int &ct[],
                           const int k)
  {
   CRowInt Ct=ct;
   MCPDSetLC(s,c,Ct,k);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CMarkovCPD::MCPDSetLC(CMCPDState &s,CMatrixDouble &c,CRowInt &ct,
                           const int k)
  {
   int n=s.m_n;
//--- check
   if(!CAp::Assert((int)CAp::Cols(c)>=n*n+1,__FUNCTION__+": Cols(C)<N*N+1"))
      return;
//--- check
   if(!CAp::Assert((int)CAp::Rows(c)>=k,__FUNCTION__+": Rows(C)<K"))
      return;
//--- check
   if(!CAp::Assert(CAp::Len(ct)>=k,__FUNCTION__+": Len(CT)<K"))
      return;
//--- check
   if(!CAp::Assert(CApServ::IsFiniteMatrix(c,k,n*n+1),__FUNCTION__+": C contains infinite or NaN values!"))
      return;
//--- copy
   s.m_c=c;
   s.m_ct=ct;
   s.m_c.Resize(k,n*n+1);
   s.m_ct.Resize(k);
   s.m_ccnt=k;
  }
//+------------------------------------------------------------------+
//| This function allows to tune amount of Tikhonov regularization   |
//| being applied to your problem.                                   |
//| By default, regularizing term is equal to r*||P-prior_P||^2,     |
//| where r is a small non-zero value,  P is transition matrix,      |
//| prior_P is identity matrix, ||X||^2 is a sum of squared elements |
//| of X.                                                            |
//| This function allows you to change coefficient r. You can also   |
//| change prior values with MCPDSetPrior() function.                |
//| INPUT PARAMETERS:                                                |
//|     S      -   solver                                            |
//|     V      -   regularization  coefficient, finite non-negative  |
//|                value. It is not recommended to specify zero      |
//|                value unless you are pretty sure that you want it.|
//+------------------------------------------------------------------+
void CMarkovCPD::MCPDSetTikhonovRegularizer(CMCPDState &s,const double v)
  {
//--- check
   if(!CAp::Assert(MathIsValidNumber(v),__FUNCTION__+": V is infinite or NAN"))
      return;
//--- check
   if(!CAp::Assert(v>=0.0,__FUNCTION__+": V is less than zero"))
      return;
//--- change value
   s.m_regterm=v;
  }
//+------------------------------------------------------------------+
//| This function allows to set prior values used for regularization |
//| of your problem.                                                 |
//| By default, regularizing term is equal to r*||P-prior_P||^2,     |
//| where r is a small non-zero value,  P is transition matrix,      |
//| prior_P is identity matrix, ||X||^2 is a sum of squared elements |
//| of X.                                                            |
//| This function allows you to change prior values prior_P. You can |
//| also change r with MCPDSetTikhonovRegularizer() function.        |
//| INPUT PARAMETERS:                                                |
//|     S       -   solver                                           |
//|     PP      -   array[N,N], matrix of prior values:              |
//|                 1. elements must be real numbers from [0,1]      |
//|                 2. columns must sum to 1.0.                      |
//|                 First property is checked (exception is thrown   |
//|                 otherwise), while second one is not              |
//|                 checked/enforced.                                |
//+------------------------------------------------------------------+
void CMarkovCPD::MCPDSetPrior(CMCPDState &s,CMatrixDouble &pp)
  {
   int n=s.m_n;
//--- check
   if(!CAp::Assert((int)CAp::Cols(pp)>=n,__FUNCTION__+": Cols(PP)<N"))
      return;
//--- check
   if(!CAp::Assert((int)CAp::Rows(pp)>=n,__FUNCTION__+": Rows(PP)<K"))
      return;
//--- check
   if(!CAp::Assert(CApServ::IsFiniteMatrix(pp,n,n),__FUNCTION__+": PP containts infinite elements"))
      return;
//--- check
   if(!CAp::Assert(pp.Min()>=0.0 && pp.Max()<=1.0,__FUNCTION__+": PP.Get(i,j) is less than 0.0 or greater than 1.0"))
      return;
//--- change value
   s.m_priorp=pp;
   s.m_priorp.Resize(n,n);
  }
//+------------------------------------------------------------------+
//| This function is used to change prediction weights               |
//| MCPD solver scales prediction errors as follows                  |
//|     Error(P) = ||W*(y-P*x)||^2                                   |
//| where                                                            |
//|     x is a system state at time t                                |
//|     y is a system state at time t+1                              |
//|     P is a transition matrix                                     |
//|     W is a diagonal scaling matrix                               |
//| By default, weights are chosen in order to minimize relative     |
//| prediction error instead of absolute one. For example, if one    |
//| component of state is about 0.5 in magnitude and another one is  |
//| about 0.05, then algorithm will make corresponding weights equal |
//| to 2.0 and 20.0.                                                 |
//| INPUT PARAMETERS:                                                |
//|     S       -   solver                                           |
//|     PW      -   array[N], weights:                               |
//|                 * must be non-negative values (exception will be |
//|                 thrown otherwise)                                |
//|                 * zero values will be replaced by automatically  |
//|                 chosen values                                    |
//+------------------------------------------------------------------+
void CMarkovCPD::MCPDSetPredictionWeights(CMCPDState &s,double &pw[])
  {
   CRowDouble Pw=pw;
   MCPDSetPredictionWeights(s,Pw);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CMarkovCPD::MCPDSetPredictionWeights(CMCPDState &s,CRowDouble &pw)
  {
   int n=s.m_n;
//--- check
   if(!CAp::Assert(CAp::Len(pw)>=n,__FUNCTION__+": Length(PW)<N"))
      return;
//--- check
   if(!CAp::Assert(CApServ::IsFiniteVector(pw,n),__FUNCTION__+": PW containts infinite or NAN elements"))
      return;
//--- check
   if(!CAp::Assert(pw.Min()>=0.0,__FUNCTION__+": PW containts negative elements"))
      return;
   s.m_pw=pw;
  }
//+------------------------------------------------------------------+
//| This function is used to start solution of the MCPD problem.     |
//| After return from this function, you can use MCPDResults() to get|
//| solution and completion code.                                    |
//+------------------------------------------------------------------+
void CMarkovCPD::MCPDSolve(CMCPDState &s)
  {
//--- create variables
   int    n=s.m_n;
   int    npairs=s.m_npairs;
   int    ccnt=0;
   int    i=0;
   int    j=0;
   int    k=0;
   int    k2=0;
   double v=0;
   double vv=0;
   int    i_=0;
   int    i1_=0;
//--- init fields of S
   s.m_repterminationtype=0;
   s.m_repinneriterationscount=0;
   s.m_repouteriterationscount=0;
   s.m_repnfev=0;
   s.m_p.Fill(AL_NaN);
//--- Generate "effective" weights for prediction and calculate preconditioner
   for(i=0; i<n; i++)
     {
      //--- check
      if(s.m_pw[i]==0.0)
        {
         //--- change values
         v=0;
         k=0;
         for(j=0; j<npairs; j++)
            //--- check
            if(s.m_data.Get(j,n+i)!=0.0)
              {
               v+=s.m_data.Get(j,n+i);
               k++;
              }
         //--- check
         if(k!=0)
            s.m_effectivew.Set(i,k/v);
         else
            s.m_effectivew.Set(i,1.0);
        }
      else
         s.m_effectivew.Set(i,s.m_pw[i]);
     }
//--- calculation
   for(i=0; i<n; i++)
     {
      for(j=0; j<n; j++)
         s.m_h.Set(i*n+j,2*s.m_regterm);
     }
//--- calculation
   for(k=0; k<npairs; k++)
     {
      for(i=0; i<n; i++)
        {
         for(j=0; j<n; j++)
            s.m_h.Add(i*n+j,2*CMath::Sqr(s.m_effectivew[i])*CMath::Sqr(s.m_data.Get(k,j)));
        }
     }
//--- calculation
   for(i=0; i<n; i++)
      for(j=0; j<n; j++)
         //--- check
         if(s.m_h[i*n+j]==0.0)
            s.m_h.Set(i*n+j,1);
//--- Generate "effective" BndL/BndU
   for(i=0; i<n; i++)
     {
      for(j=0; j<n; j++)
        {
         //--- Set default boundary constraints.
         //--- Lower bound is always zero,upper bound is calculated
         //--- with respect to entry/exit states.
         s.m_effectivebndl.Set(i*n+j,0.0);
         //--- check
         if(s.m_states[i]>0 || s.m_states[j]<0)
            s.m_effectivebndu.Set(i*n+j,0.0);
         else
            s.m_effectivebndu.Set(i*n+j,1.0);
         //--- Calculate intersection of the default and user-specified bound constraints.
         //--- This code checks consistency of such combination.
         if(MathIsValidNumber(s.m_bndl.Get(i,j)) && s.m_bndl.Get(i,j)>s.m_effectivebndl[i*n+j])
            s.m_effectivebndl.Set(i*n+j,s.m_bndl.Get(i,j));
         //--- check
         if(MathIsValidNumber(s.m_bndu.Get(i,j)) && s.m_bndu.Get(i,j)<s.m_effectivebndu[i*n+j])
            s.m_effectivebndu.Set(i*n+j,s.m_bndu.Get(i,j));
         //--- check
         if(s.m_effectivebndl[i*n+j]>s.m_effectivebndu[i*n+j])
           {
            s.m_repterminationtype=-3;
            return;
           }
         //--- Calculate intersection of the effective bound constraints
         //--- and user-specified equality constraints.
         //--- This code checks consistency of such combination.
         if(MathIsValidNumber(s.m_ec.Get(i,j)))
           {
            //--- check
            if(s.m_ec.Get(i,j)<s.m_effectivebndl[i*n+j] || s.m_ec.Get(i,j)>s.m_effectivebndu[i*n+j])
              {
               s.m_repterminationtype=-3;
               return;
              }
            //--- change values
            s.m_effectivebndl.Set(i*n+j,s.m_ec.Get(i,j));
            s.m_effectivebndu.Set(i*n+j,s.m_ec.Get(i,j));
           }
        }
     }
//--- Generate linear constraints:
//---*"default" sums-to-one constraints (not generated for "exit" states)
   CApServ::RMatrixSetLengthAtLeast(s.m_effectivec,s.m_ccnt+n,n*n+1);
//--- function call
   CApServ::IVectorSetLengthAtLeast(s.m_effectivect,s.m_ccnt+n);
   ccnt=s.m_ccnt;
   for(i=0; i<s.m_ccnt; i++)
     {
      for(j=0; j<=n*n; j++)
         s.m_effectivec.Set(i,j,s.m_c.Get(i,j));
      s.m_effectivect.Set(i,s.m_ct[i]);
     }
//--- calculation
   for(i=0; i<n; i++)
     {
      //--- check
      if(s.m_states[i]>=0)
        {
         for(k=0; k<n*n; k++)
            s.m_effectivec.Set(ccnt,k,0);
         for(k=0; k<n; k++)
            s.m_effectivec.Set(ccnt,k*n+i,1);
         //--- change values
         s.m_effectivec.Set(ccnt,n*n,1.0);
         s.m_effectivect.Set(ccnt,0);
         ccnt++;
        }
     }
//--- create optimizer
   for(i=0; i<n; i++)
      for(j=0; j<n; j++)
         s.m_tmpp.Set(i*n+j,1.0/(double)n);
//--- function calls
   CMinBLEIC::MinBLEICRestartFrom(s.m_bs,s.m_tmpp);
   CMinBLEIC::MinBLEICSetBC(s.m_bs,s.m_effectivebndl,s.m_effectivebndu);
   CMinBLEIC::MinBLEICSetLC(s.m_bs,s.m_effectivec,s.m_effectivect,ccnt);
   CMinBLEIC::MinBLEICSetInnerCond(s.m_bs,0,0,m_xtol);
   CMinBLEIC::MinBLEICSetOuterCond(s.m_bs,m_xtol,1.0E-5);
   CMinBLEIC::MinBLEICSetPrecDiag(s.m_bs,s.m_h);
//--- solve problem
   while(CMinBLEIC::MinBLEICIteration(s.m_bs))
     {
      //--- check
      if(!CAp::Assert(s.m_bs.m_needfg,__FUNCTION__+": internal error"))
         return;
      //--- check
      if(s.m_bs.m_needfg)
        {
         //--- Calculate regularization term
         s.m_bs.m_f=0.0;
         vv=s.m_regterm;
         for(i=0; i<n; i++)
            for(j=0; j<n; j++)
              {
               double delt=s.m_bs.m_x[i*n+j]-s.m_priorp.Get(i,j);
               s.m_bs.m_f+=vv*CMath::Sqr(delt);
               s.m_bs.m_g.Set(i*n+j,2*vv*delt);
              }
         //--- calculate prediction error/gradient for K-th pair
         for(k=0; k<npairs; k++)
           {
            for(i=0; i<n; i++)
              {
               i1_=-(i*n);
               v=0.0;
               for(i_=i*n; i_<i*n+n; i_++)
                  v+=s.m_bs.m_x[i_]*s.m_data.Get(k,i_+i1_);
               vv=s.m_effectivew[i];
               s.m_bs.m_f+=CMath::Sqr(vv*(v-s.m_data.Get(k,n+i)));
               for(j=0; j<n; j++)
                  s.m_bs.m_g.Add(i*n+j,2*vv*vv*(v-s.m_data.Get(k,n+i))*s.m_data.Get(k,j));
              }
           }
         //--- continue
         continue;
        }
     }
//--- function call
   CMinBLEIC::MinBLEICResultsBuf(s.m_bs,s.m_tmpp,s.m_br);
   for(i=0; i<n; i++)
     {
      for(j=0; j<n; j++)
         s.m_p.Set(i,j,s.m_tmpp[i*n+j]);
     }
//--- change values
   s.m_repterminationtype=s.m_br.m_terminationtype;
   s.m_repinneriterationscount=s.m_br.m_inneriterationscount;
   s.m_repouteriterationscount=s.m_br.m_outeriterationscount;
   s.m_repnfev=s.m_br.m_nfev;
  }
//+------------------------------------------------------------------+
//| MCPD results                                                     |
//| INPUT PARAMETERS:                                                |
//|     State   -   algorithm state                                  |
//| OUTPUT PARAMETERS:                                               |
//|     P      -   array[N,N], transition matrix                     |
//|     Rep    -   optimization report. You should check Rep.        |
//|                TerminationType in order to distinguish successful|
//|                termination from unsuccessful one. Speaking short,|
//|                positive values denote success, negative ones are |
//|                failures. More information about fields of this   |
//|                structure  can befound in the comments on         |
//|                MCPDReport datatype.                              |
//+------------------------------------------------------------------+
void CMarkovCPD::MCPDResults(CMCPDState &s,CMatrixDouble &p,
                             CMCPDReport &rep)
  {
//--- copy
   p=s.m_p;
//--- change values
   rep.m_terminationtype=s.m_repterminationtype;
   rep.m_inneriterationscount=s.m_repinneriterationscount;
   rep.m_outeriterationscount=s.m_repouteriterationscount;
   rep.m_nfev=s.m_repnfev;
  }
//+------------------------------------------------------------------+
//| Internal initialization function                                 |
//+------------------------------------------------------------------+
void CMarkovCPD::MCPDInit(const int n,const int entrystate,
                          const int exitstate,CMCPDState &s)
  {
//--- check
   if(!CAp::Assert(n>=1,__FUNCTION__+": N<1"))
      return;
//--- initialization
   s.m_n=n;
//--- allocation
   s.m_states.Resize(n);
   s.m_states.Fill(0);
//--- check
   if(entrystate>=0)
      s.m_states.Set(entrystate,1);
//--- check
   if(exitstate>=0)
      s.m_states.Set(exitstate,-1);
//--- initialization
   s.m_npairs=0;
   s.m_regterm=1.0E-8;
   s.m_ccnt=0;
//--- allocation
   s.m_p=matrix<double>::Zeros(n,n);
   s.m_ec=matrix<double>::Full(n,n,CInfOrNaN::NaN());
   s.m_bndl=matrix<double>::Full(n,n,CInfOrNaN::NegativeInfinity());
   s.m_bndu=matrix<double>::Full(n,n,CInfOrNaN::PositiveInfinity());
   s.m_pw=vector<double>::Zeros(n);
   s.m_priorp=matrix<double>::Identity(n,n);
   s.m_tmpp=vector<double>::Zeros(n*n);
   s.m_effectivew.Resize(n);
   s.m_effectivebndl.Resize(n*n);
   s.m_effectivebndu.Resize(n*n);
   s.m_h.Resize(n*n);
//--- allocation
   s.m_data=matrix<double>::Zeros(1,2*n);
//--- function call
   CMinBLEIC::MinBLEICCreate(n*n,s.m_tmpp,s.m_bs);
  }
//+------------------------------------------------------------------+
//| Training report:                                                 |
//|   * RelCLSError     -  fraction of misclassified cases.          |
//|   * AvgCE           -  acerage cross-entropy                     |
//|   * RMSError        -  root-mean-square error                    |
//|   * AvgError        -  average error                             |
//|   * AvgRelError     -  average relative error                    |
//|   * NGrad           -  number of gradient calculations           |
//|   * NHess           -  number of Hessian calculations            |
//|   * NCholesky       -  number of Cholesky decompositions         |
//| NOTE 1: RelCLSError/AvgCE are zero on regression problems.       |
//| NOTE 2: on classification problems RMSError/AvgError/AvgRelError |
//|         contain errors in prediction of posterior probabilities  |
//+------------------------------------------------------------------+
class CMLPReport
  {
public:
   //--- variables
   double            m_RelCLSError;
   double            m_AvgCE;
   double            m_RMSError;
   double            m_AvgError;
   double            m_AvgRelError;
   int               m_ngrad;
   int               m_nhess;
   int               m_ncholesky;
   //--- constructor, destructor
                     CMLPReport(void) { ZeroMemory(this); }
                    ~CMLPReport(void) {}
   //--- copy
   void              Copy(const CMLPReport &obj);
   //--- overloading
   void              operator=(const CMLPReport &obj) { Copy(obj); }
  };
//+------------------------------------------------------------------+
//| Copy                                                             |
//+------------------------------------------------------------------+
void CMLPReport::Copy(const CMLPReport &obj)
  {
//--- copy variables
   m_RelCLSError=obj.m_RelCLSError;
   m_AvgCE=obj.m_AvgCE;
   m_RMSError=obj.m_RMSError;
   m_AvgError=obj.m_AvgError;
   m_AvgRelError=obj.m_AvgRelError;
   m_ngrad=obj.m_ngrad;
   m_nhess=obj.m_nhess;
   m_ncholesky=obj.m_ncholesky;
  }
//+------------------------------------------------------------------+
//| Training report:                                                 |
//|     * NGrad     - number of gradient calculations                |
//|     * NHess     - number of Hessian calculations                 |
//|     * NCholesky - number of Cholesky decompositions              |
//+------------------------------------------------------------------+
class CMLPReportShell
  {
private:
   CMLPReport        m_innerobj;

public:
   //--- constructors, destructor
                     CMLPReportShell(void) {}
                     CMLPReportShell(CMLPReport &obj) { m_innerobj.Copy(obj); }
                    ~CMLPReportShell(void) {}
   //--- methods
   int               GetNGrad(void);
   void              SetNGrad(const int i);
   int               GetNHess(void);
   void              SetNHess(const int i);
   int               GetNCholesky(void);
   void              SetNCholesky(const int i);
   CMLPReport       *GetInnerObj(void);
  };
//+------------------------------------------------------------------+
//| Returns the value of the variable ngrad                          |
//+------------------------------------------------------------------+
int CMLPReportShell::GetNGrad(void)
  {
   return(m_innerobj.m_ngrad);
  }
//+------------------------------------------------------------------+
//| Changing the value of the variable ngrad                         |
//+------------------------------------------------------------------+
void CMLPReportShell::SetNGrad(const int i)
  {
   m_innerobj.m_ngrad=i;
  }
//+------------------------------------------------------------------+
//| Returns the value of the variable nhess                          |
//+------------------------------------------------------------------+
int CMLPReportShell::GetNHess(void)
  {
   return(m_innerobj.m_nhess);
  }
//+------------------------------------------------------------------+
//| Changing the value of the variable nhess                         |
//+------------------------------------------------------------------+
void CMLPReportShell::SetNHess(const int i)
  {
   m_innerobj.m_nhess=i;
  }
//+------------------------------------------------------------------+
//| Returns the value of the variable ncholesky                      |
//+------------------------------------------------------------------+
int CMLPReportShell::GetNCholesky(void)
  {
   return(m_innerobj.m_ncholesky);
  }
//+------------------------------------------------------------------+
//| Changing the value of the variable ncholesky                     |
//+------------------------------------------------------------------+
void CMLPReportShell::SetNCholesky(const int i)
  {
   m_innerobj.m_ncholesky=i;
  }
//+------------------------------------------------------------------+
//| Return object of class                                           |
//+------------------------------------------------------------------+
CMLPReport *CMLPReportShell::GetInnerObj(void)
  {
   return(GetPointer(m_innerobj));
  }
//+------------------------------------------------------------------+
//| Cross-validation estimates of generalization error               |
//+------------------------------------------------------------------+
class CMLPCVReport
  {
public:
   //--- variables
   double            m_RelCLSError;
   double            m_AvgCE;
   double            m_RMSError;
   double            m_AvgError;
   double            m_AvgRelError;
   //--- constructor, destructor
                     CMLPCVReport(void) { ZeroMemory(this); }
                    ~CMLPCVReport(void) {}
   //--- copy
   void              Copy(const CMLPCVReport &obj);
   //--- overloading
   void              operator=(const CMLPCVReport &obj) { Copy(obj); }
  };
//+------------------------------------------------------------------+
//| Copy                                                             |
//+------------------------------------------------------------------+
void CMLPCVReport::Copy(const CMLPCVReport &obj)
  {
//--- copy variables
   m_RelCLSError=obj.m_RelCLSError;
   m_AvgCE=obj.m_AvgCE;
   m_RMSError=obj.m_RMSError;
   m_AvgError=obj.m_AvgError;
   m_AvgRelError=obj.m_AvgRelError;
  }
//+------------------------------------------------------------------+
//| Cross-validation estimates of generalization error               |
//+------------------------------------------------------------------+
class CMLPCVReportShell
  {
private:
   CMLPCVReport      m_innerobj;

public:
   //--- constructors, destructor
                     CMLPCVReportShell(void) {}
                     CMLPCVReportShell(CMLPCVReport &obj) { m_innerobj.Copy(obj); }
                    ~CMLPCVReportShell(void) {}
   //--- methods
   double            GetRelClsError(void);
   void              SetRelClsError(const double d);
   double            GetAvgCE(void);
   void              SetAvgCE(const double d);
   double            GetRMSError(void);
   void              SetRMSError(const double d);
   double            GetAvgError(void);
   void              SetAvgError(const double d);
   double            GetAvgRelError(void);
   void              SetAvgRelError(const double d);
   CMLPCVReport     *GetInnerObj(void);
  };
//+------------------------------------------------------------------+
//| Returns the value of the variable relclserror                    |
//+------------------------------------------------------------------+
double CMLPCVReportShell::GetRelClsError(void)
  {
   return(m_innerobj.m_RelCLSError);
  }
//+------------------------------------------------------------------+
//| Changing the value of the variable relclserror                   |
//+------------------------------------------------------------------+
void CMLPCVReportShell::SetRelClsError(const double d)
  {
   m_innerobj.m_RelCLSError=d;
  }
//+------------------------------------------------------------------+
//| Returns the value of the variable avgce                          |
//+------------------------------------------------------------------+
double CMLPCVReportShell::GetAvgCE(void)
  {
   return(m_innerobj.m_AvgCE);
  }
//+------------------------------------------------------------------+
//| Changing the value of the variable avgce                         |
//+------------------------------------------------------------------+
void CMLPCVReportShell::SetAvgCE(const double d)
  {
   m_innerobj.m_AvgCE=d;
  }
//+------------------------------------------------------------------+
//| Returns the value of the variable rmserror                       |
//+------------------------------------------------------------------+
double CMLPCVReportShell::GetRMSError(void)
  {
   return(m_innerobj.m_RMSError);
  }
//+------------------------------------------------------------------+
//| Changing the value of the variable rmserror                      |
//+------------------------------------------------------------------+
void CMLPCVReportShell::SetRMSError(const double d)
  {
   m_innerobj.m_RMSError=d;
  }
//+------------------------------------------------------------------+
//| Returns the value of the variable avgerror                       |
//+------------------------------------------------------------------+
double CMLPCVReportShell::GetAvgError(void)
  {
   return(m_innerobj.m_AvgError);
  }
//+------------------------------------------------------------------+
//| Changing the value of the variable avgerror                      |
//+------------------------------------------------------------------+
void CMLPCVReportShell::SetAvgError(const double d)
  {
   m_innerobj.m_AvgError=d;
  }
//+------------------------------------------------------------------+
//| Returns the value of the variable avgrelerror                    |
//+------------------------------------------------------------------+
double CMLPCVReportShell::GetAvgRelError(void)
  {
   return(m_innerobj.m_AvgRelError);
  }
//+------------------------------------------------------------------+
//| Changing the value of the variable avgrelerror                   |
//+------------------------------------------------------------------+
void CMLPCVReportShell::SetAvgRelError(const double d)
  {
   m_innerobj.m_AvgRelError=d;
  }
//+------------------------------------------------------------------+
//| Return object of class                                           |
//+------------------------------------------------------------------+
CMLPCVReport *CMLPCVReportShell::GetInnerObj(void)
  {
   return(GetPointer(m_innerobj));
  }
//+------------------------------------------------------------------+
//| Neural networks ensemble                                         |
//+------------------------------------------------------------------+
class CMLPEnsemble
  {
public:
   //--- variables
   int                     m_ensemblesize;
   CMultilayerPerceptron   m_network;
   //--- arrays
   CRowDouble              m_weights;
   CRowDouble              m_columnmeans;
   CRowDouble              m_columnsigmas;
   CRowDouble              m_y;
   //--- constructor, destructor
                     CMLPEnsemble(void) { m_ensemblesize=0; }
                    ~CMLPEnsemble(void) {}
   //--- copy
   void              Copy(const CMLPEnsemble &obj);
   //--- overloading
   void              operator=(const CMLPEnsemble &obj) { Copy(obj); }
  };
//+------------------------------------------------------------------+
//| Copy                                                             |
//+------------------------------------------------------------------+
void CMLPEnsemble::Copy(const CMLPEnsemble &obj)
  {
//--- copy variables
   m_ensemblesize=obj.m_ensemblesize;
   CMLPBase::MLPCopy(obj.m_network,m_network);
//--- copy arrays
   m_weights=obj.m_weights;
   m_columnmeans=obj.m_columnmeans;
   m_columnsigmas=obj.m_columnsigmas;
   m_y=obj.m_y;
  }
//+------------------------------------------------------------------+
//| Neural networks ensemble                                         |
//+------------------------------------------------------------------+
class CMLPEnsembleShell
  {
private:
   CMLPEnsemble      m_innerobj;

public:
   //--- constructors, destructor
                     CMLPEnsembleShell(void) {}
                     CMLPEnsembleShell(CMLPEnsemble &obj) { m_innerobj.Copy(obj); }
                    ~CMLPEnsembleShell(void) {}
   //--- method
   CMLPEnsemble     *GetInnerObj(void) { return(GetPointer(m_innerobj)); }
  };
//+------------------------------------------------------------------+
//| Temporary data structures used by following functions:           |
//|   * TrainNetworkX                                                |
//|   * StartTrainingX                                               |
//|   * ContinueTrainingX                                            |
//| This structure contains:                                         |
//|   * network being trained                                        |
//|   * fully initialized LBFGS optimizer (we have to call           |
//|     MinLBFGSRestartFrom() before using it; usually it is done    |
//|     by StartTrainingX() function).                               |
//|   * additional temporary arrays                                  |
//| This structure should be initialized with InitMLPTrnSession()    |
//| call.                                                            |
//+------------------------------------------------------------------+
struct CSMLPTrnSession
  {
   CRowDouble        m_bestparameters;
   double            m_bestrmserror;
   bool              m_randomizenetwork;
   CMultilayerPerceptron m_network;
   CMinLBFGSState    m_optimizer;
   CMinLBFGSReport   m_optimizerrep;
   CRowDouble        m_wbuf0;
   CRowDouble        m_wbuf1;
   CRowInt           m_allminibatches;
   CRowInt           m_currentminibatch;
   RCommState        m_rstate;
   int               m_algoused;
   int               m_minibatchsize;
   CMLPReport        m_mlprep;
   CRowInt           m_trnsubset;
   CRowInt           m_valsubset;
   CHighQualityRandState m_generator;
   //--- constructor / destructor
                     CSMLPTrnSession(void);
                    ~CSMLPTrnSession(void) {}
   //---
   void              Copy(const CSMLPTrnSession &obj);
   //--- overloading
   void              operator=(const CSMLPTrnSession &obj) { Copy(obj); }
  };
//+------------------------------------------------------------------+
//| Coonstructor                                                     |
//+------------------------------------------------------------------+
CSMLPTrnSession::CSMLPTrnSession(void)
  {
   m_bestrmserror=0;
   m_randomizenetwork=false;
   m_algoused=0;
   m_minibatchsize=0;
  }
//+------------------------------------------------------------------+
//| Copy                                                             |
//+------------------------------------------------------------------+
void CSMLPTrnSession::Copy(const CSMLPTrnSession &obj)
  {
   m_bestparameters=obj.m_bestparameters;
   m_bestrmserror=obj.m_bestrmserror;
   m_randomizenetwork=obj.m_randomizenetwork;
   m_network=obj.m_network;
   m_optimizer=obj.m_optimizer;
   m_optimizerrep=obj.m_optimizerrep;
   m_wbuf0=obj.m_wbuf0;
   m_wbuf1=obj.m_wbuf1;
   m_allminibatches=obj.m_allminibatches;
   m_currentminibatch=obj.m_currentminibatch;
   m_rstate=obj.m_rstate;
   m_algoused=obj.m_algoused;
   m_minibatchsize=obj.m_minibatchsize;
   m_mlprep=obj.m_mlprep;
   m_trnsubset=obj.m_trnsubset;
   m_valsubset=obj.m_valsubset;
   m_generator=obj.m_generator;
  }
//+------------------------------------------------------------------+
//| Trainer object for neural network.                               |
//| You should not try to access fields of this object directly - use|
//| ALGLIB functions to work with this object.                       |
//+------------------------------------------------------------------+
struct CMLPTrainer
  {
   int               m_nin;
   int               m_nout;
   bool              m_rcpar;
   int               m_lbfgsfactor;
   double            m_decay;
   double            m_wstep;
   int               m_maxits;
   int               m_datatype;
   int               m_npoints;
   CMatrixDouble     m_densexy;
   CSparseMatrix     m_sparsexy;
   CSMLPTrnSession   m_session;
   int               m_ngradbatch;
   CRowInt           m_subset;
   int               m_subsetsize;
   CRowInt           m_valsubset;
   int               m_valsubsetsize;
   int               m_algokind;
   int               m_minibatchsize;
   //--- constructor / destructor
                     CMLPTrainer(void);
                    ~CMLPTrainer(void) {}
   void              Copy(const CMLPTrainer &obj);
   //--- overloading
   void              operator=(const CMLPTrainer &obj) { Copy(obj); }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CMLPTrainer::CMLPTrainer(void)
  {
   m_nin=0;
   m_nout=0;
   m_rcpar=false;
   m_lbfgsfactor=0;
   m_decay=0;
   m_wstep=0;
   m_maxits=0;
   m_datatype=0;
   m_npoints=0;
   m_ngradbatch=0;
   m_subsetsize=0;
   m_valsubsetsize=0;
   m_algokind=0;
   m_minibatchsize=0;
  }
//+------------------------------------------------------------------+
//| Copy                                                             |
//+------------------------------------------------------------------+
void CMLPTrainer::Copy(const CMLPTrainer &obj)
  {
   m_nin=obj.m_nin;
   m_nout=obj.m_nout;
   m_rcpar=obj.m_rcpar;
   m_lbfgsfactor=obj.m_lbfgsfactor;
   m_decay=obj.m_decay;
   m_wstep=obj.m_wstep;
   m_maxits=obj.m_maxits;
   m_datatype=obj.m_datatype;
   m_npoints=obj.m_npoints;
   m_densexy=obj.m_densexy;
   m_sparsexy=obj.m_sparsexy;
   m_session=obj.m_session;
   m_ngradbatch=obj.m_ngradbatch;
   m_subset=obj.m_subset;
   m_subsetsize=obj.m_subsetsize;
   m_valsubset=obj.m_valsubset;
   m_valsubsetsize=obj.m_valsubsetsize;
   m_algokind=obj.m_algokind;
   m_minibatchsize=obj.m_minibatchsize;
  }
//+------------------------------------------------------------------+
//| Training neural networks                                         |
//+------------------------------------------------------------------+
class CMLPTrain
  {
public:
   //--- constant
   static const double m_mindecay;
   static const int    m_defaultlbfgsfactor;
   //--- public methods
   static void       MLPTrainLM(CMultilayerPerceptron &network,CMatrixDouble &xy,const int npoints,double decay,const int restarts,int &info,CMLPReport &rep);
   static void       MLPTrainLBFGS(CMultilayerPerceptron &network,CMatrixDouble &xy,const int npoints,double decay,const int restarts,const double wstep,int maxits,int &info,CMLPReport &rep);
   static void       MLPTrainES(CMultilayerPerceptron &network,CMatrixDouble &trnxy,const int trnsize,CMatrixDouble &valxy,const int valsize,const double decay,int restarts,int &info,CMLPReport &rep);
   static void       MLPKFoldCVLBFGS(CMultilayerPerceptron &network,CMatrixDouble &xy,const int npoints,const double decay,const int restarts,const double wstep,const int maxits,const int foldscount,int &info,CMLPReport &rep,CMLPCVReport &cvrep);
   static void       MLPKFoldCVLM(CMultilayerPerceptron &network,CMatrixDouble &xy,const int npoints,const double decay,const int restarts,int foldscount,int &info,CMLPReport &rep,CMLPCVReport &cvrep);
   static void       MLPCreateTrainer(int nin,int nout,CMLPTrainer &s);
   static void       MLPCreateTrainerCls(int nin,int nclasses,CMLPTrainer &s);
   static void       MLPSetDataset(CMLPTrainer &s,CMatrixDouble &xy,int npoints);
   static void       MLPSetSparseDataset(CMLPTrainer &s,CSparseMatrix &xy,int npoints);
   static void       MLPSetDecay(CMLPTrainer &s,double decay);
   static void       MLPSetCond(CMLPTrainer &s,double wstep,int maxits);
   static void       MLPSetAlgoBatch(CMLPTrainer &s);
   static void       MLPTrainNetwork(CMLPTrainer &s,CMultilayerPerceptron &network,int nrestarts,CMLPReport &rep);
   static void       MLPStartTraining(CMLPTrainer &s,CMultilayerPerceptron &network,bool randomstart);
   static bool       MLPContinueTraining(CMLPTrainer &s,CMultilayerPerceptron &network);
   static void       MLPEBaggingLM(CMLPEnsemble &ensemble,CMatrixDouble &xy,const int npoints,const double decay,const int restarts,int &info,CMLPReport &rep,CMLPCVReport &ooberrors);
   static void       MLPEBaggingLBFGS(CMLPEnsemble &ensemble,CMatrixDouble &xy,const int npoints,const double decay,const int restarts,const double wstep,const int maxits,int &info,CMLPReport &rep,CMLPCVReport &ooberrors);
   static void       MLPETrainES(CMLPEnsemble &ensemble,CMatrixDouble &xy,const int npoints,const double decay,const int restarts,int &info,CMLPReport &rep);
   static void       MLPTrainEnsembleES(CMLPTrainer &s,CMLPEnsemble &ensemble,int nrestarts,CMLPReport &rep);

private:
   static void       MLPKFoldCVGeneral(CMultilayerPerceptron &n,CMatrixDouble &xy,const int npoints,const double decay,const int restarts,const int foldscount,const bool lmalgorithm,const double wstep,const int maxits,int &info,CMLPReport &rep,CMLPCVReport &cvrep);
   static void       MLPKFoldSplit(CMatrixDouble &xy,const int npoints,const int nclasses,const int foldscount,const bool stratifiedsplits,CRowInt &folds);
   static void       MLPTrainNetworkX(CMLPTrainer &s,int nrestarts,int algokind,CRowInt &trnsubset,int trnsubsetsize,CRowInt &valsubset,int valsubsetsize,CMultilayerPerceptron &network,CMLPReport &rep);
   static void       MLPTrainEnsembleX(CMLPTrainer &s,CMLPEnsemble &ensemble,int idx0,int idx1,int nrestarts,int trainingmethod,int ngrad);
   static void       MLPStartTrainingX(CMLPTrainer &s,bool randomstart,int algokind,CRowInt &subset,int subsetsize,CSMLPTrnSession &session);
   static bool       MLPContinueTrainingX(CMLPTrainer &s,CRowInt &subset,int subsetsize,int &ngradbatch,CSMLPTrnSession &session);
   static bool       MLPContinueTrainingX_lbl1(CMLPTrainer &s,CRowInt &subset,int subsetsize,int &ngradbatch,CSMLPTrnSession &session,double &decay);
   static bool       MLPContinueTrainingX_lbl3(CMLPTrainer &s,CRowInt &subset,int subsetsize,int &ngradbatch,CSMLPTrnSession &session,double &decay);
   static void       MLPEBaggingInternal(CMLPEnsemble &ensemble,CMatrixDouble &xy,int npoints,double decay,int restarts,double wstep,int maxits,bool lmalgorithm,int &info,CMLPReport &rep,CMLPCVReport &ooberrors);
   static void       InitMLPTrnSession(CMultilayerPerceptron &networktrained,bool randomizenetwork,CMLPTrainer &trainer,CSMLPTrnSession &session);
  };
//+------------------------------------------------------------------+
//| Initialize constant                                              |
//+------------------------------------------------------------------+
const double CMLPTrain::m_mindecay=0.001;
const int CMLPTrain::m_defaultlbfgsfactor=6;
//+------------------------------------------------------------------+
//| Neural network training using modified Levenberg-Marquardt with  |
//| exact Hessian calculation and regularization. Subroutine trains  |
//| neural network with restarts from random positions. Algorithm is |
//| well suited for small                                            |
//| and medium scale problems (hundreds of weights).                 |
//| INPUT PARAMETERS:                                                |
//|     Network     -   neural network with initialized geometry     |
//|     XY          -   training set                                 |
//|     NPoints     -   training set size                            |
//|     Decay       -   weight decay constant, >=0.001               |
//|                     Decay term 'Decay*||Weights||^2' is added to |
//|                     error function.                              |
//|                     If you don't know what Decay to choose, use  |
//|                     0.001.                                       |
//|     Restarts    -   number of restarts from random position, >0. |
//|                     If you don't know what Restarts to choose,   |
//|                     use 2.                                       |
//| OUTPUT PARAMETERS:                                               |
//|     Network     -   trained neural network.                      |
//|     Info        -   return code:                                 |
//|                     * -9, if internal matrix inverse subroutine  |
//|                           failed                                 |
//|                     * -2, if there is a point with class number  |
//|                           outside of [0..NOut-1].                |
//|                     * -1, if wrong parameters specified          |
//|                           (NPoints<0, Restarts<1).               |
//|                     *  2, if task has been solved.               |
//|     Rep         -   training report                              |
//+------------------------------------------------------------------+
void CMLPTrain::MLPTrainLM(CMultilayerPerceptron &network,CMatrixDouble &xy,
                           const int npoints,double decay,const int restarts,
                           int &info,CMLPReport &rep)
  {
//--- create variables
   int    nin=0;
   int    nout=0;
   int    wcount=0;
   double lmm_ftol=0;
   double lmsteptol=0;
   int    i=0;
   int    k=0;
   double v=0;
   double e=0;
   double enew=0;
   double xnorm2=0;
   double stepnorm=0;
   bool   spd;
   double nu=0;
   double lambdav=0;
   double lambdaup=0;
   double lambdadown=0;
   int    pass=0;
   double ebest=0;
   int    invinfo=0;
   int    solverinfo=0;
   int    i_=0;
//--- creating arrays
   CRowDouble g;
   vector<double> d;
   vector<double> x;
   vector<double> y;
   CRowDouble wbase;
   CRowDouble wdir;
   CRowDouble wt;
   vector<double> wx;
   CRowDouble wbest;
//--- create matrix
   CMatrixDouble h;
   CMatrixDouble hmod;
   CMatrixDouble z;
//--- objects of classes
   CMinLBFGSReport    internalrep;
   CMinLBFGSState     state;
   CMatInvReport      invrep;
   CDenseSolverReport solverrep;
//--- initialization
   info=0;
//--- function call
   CMLPBase::MLPProperties(network,nin,nout,wcount);
//--- initialization
   lambdaup=10;
   lambdadown=0.3;
   lmm_ftol=0.001;
   lmsteptol=0.001;
//--- Test for inputs
   if(npoints<=0 || restarts<1)
     {
      info=-1;
      return;
     }
//--- check
   if(CMLPBase::MLPIsSoftMax(network))
     {
      y=xy.Col(nin);
      //--- check
      if((int)MathRound(y.Min())<0 || (int)MathRound(y.Max())>=nout)
        {
         info=-2;
         return;
        }
     }
//--- change values
   decay=MathMax(decay,m_mindecay);
   info=2;
//--- Initialize data
   rep.m_ngrad=0;
   rep.m_nhess=0;
   rep.m_ncholesky=0;
//--- General case.
//--- Prepare task and network. Allocate space.
   CMLPBase::MLPInitPreprocessor(network,xy,npoints);
//--- allocation
   g.Resize(wcount);
   h.Resize(wcount,wcount);
   hmod.Resize(wcount,wcount);
   wbase.Resize(wcount);
   wdir.Resize(wcount);
   wbest.Resize(wcount);
   wt.Resize(wcount);
   wx.Resize(wcount);
//--- initialization
   ebest=CMath::m_maxrealnumber;
//--- Multiple passes
   for(pass=1; pass<=restarts; pass++)
     {
      //--- Initialize weights
      CMLPBase::MLPRandomize(network);
      //--- First stage of the hybrid algorithm: LBFGS
      wbase=network.m_weights;
      //--- function calls
      CMinLBFGS::MinLBFGSCreate(wcount,(int)(MathMin(wcount,5)),wbase,state);
      CMinLBFGS::MinLBFGSSetCond(state,0,0,0,(int)(MathMax(25,wcount)));
      while(CMinLBFGS::MinLBFGSIteration(state))
        {
         //--- gradient
         network.m_weights=state.m_x;
         //--- function call
         CMLPBase::MLPGradBatch(network,xy,npoints,state.m_f,state.m_g);
         //--- weight decay
         v=network.m_weights.Dot(network.m_weights);
         state.m_f+=0.5*decay*v;
         state.m_g+=network.m_weights*decay+0;
         //--- next iteration
         rep.m_ngrad++;
        }
      //--- function call
      CMinLBFGS::MinLBFGSResults(state,wbase,internalrep);
      network.m_weights=wbase;
      //--- Second stage of the hybrid algorithm: LM
      //--- Initialize H with identity matrix,
      //--- G with gradient,
      //--- E with regularized error.
      CMLPBase::MLPHessianBatch(network,xy,npoints,e,g,h);
      v=network.m_weights.Dot(network.m_weights);
      //--- change values
      e=e+0.5*decay*v;
      g+=network.m_weights*decay+0;
      h.Diag(h.Diag()+decay);
      //--- change values
      rep.m_nhess++;
      lambdav=0.001;
      nu=2;
      //--- cycle
      while(true)
        {
         //--- 1. HMod=H+lambda*I
         //--- 2. Try to solve (H+Lambda*I)*dx=-g.
         //---    Increase lambda if left part is not positive definite.
         hmod=h;
         hmod.Diag(hmod.Diag(0)+lambdav);
         //--- function call
         spd=CTrFac::SPDMatrixCholesky(hmod,wcount,true);
         rep.m_ncholesky++;
         //--- check
         if(!spd)
           {
            lambdav=lambdav*lambdaup*nu;
            nu*=2;
            continue;
           }
         //--- function call
         CDenseSolver::SPDMatrixCholeskySolve(hmod,wcount,true,g,solverinfo,solverrep,wdir);
         //--- check
         if(solverinfo<0)
           {
            lambdav=lambdav*lambdaup*nu;
            nu*=2;
            continue;
           }
         wdir*=(-1);
         //--- Lambda found.
         //--- 1. Save old w in WBase
         //--- 1. Test some stopping criterions
         //--- 2. If error(w+wdir)>error(w),increase lambda
         network.m_weights+=wdir;
         xnorm2=network.m_weights.Dot(network.m_weights);
         //--- change value
         stepnorm=MathSqrt(wdir.Dot(wdir));
         //--- function call
         enew=CMLPBase::MLPError(network,xy,npoints)+0.5*decay*xnorm2;
         //--- check
         if(stepnorm<lmsteptol*(1+MathSqrt(xnorm2)))
            break;
         //--- check
         if(enew>e)
           {
            lambdav*=lambdaup*nu;
            nu*=2;
            continue;
           }
         //--- Optimize using inv(cholesky(H)) as preconditioner
         CMatInv::RMatrixTrInverse(hmod,wcount,true,false,invinfo,invrep);
         //--- check
         if(invinfo<=0)
           {
            //--- if matrix can't be inverted then exit with errors
            //--- TODO: make WCount steps in direction suggested by HMod
            info=-9;
            return;
           }
         //--- calculation
         wbase=network.m_weights;
         wt=vector<double>::Zeros(wcount);
         //--- function calls
         CMinLBFGS::MinLBFGSCreateX(wcount,wcount,wt,1,0.0,state);
         CMinLBFGS::MinLBFGSSetCond(state,0,0,0,5);
         while(CMinLBFGS::MinLBFGSIteration(state))
           {
            //--- gradient
            network.m_weights=wbase.ToVector()+state.m_x.MatMul(hmod.Transpose()+0);
            //--- function call
            CMLPBase::MLPGradBatch(network,xy,npoints,state.m_f,g);
            state.m_g=g.MatMul(hmod)+0;
            //--- weight decay
            //--- grad(x'*x)=A'*(x0+A*t)
            v=network.m_weights.Dot(network.m_weights);
            state.m_f+=0.5*decay*v;
            for(i=0; i<wcount; i++)
              {
               v=decay*network.m_weights[i];
               state.m_g+=hmod[i]*v;
              }
            //--- next iteration
            rep.m_ngrad++;
           }
         //--- function call
         CMinLBFGS::MinLBFGSResults(state,wt,internalrep);
         //--- Accept new position.
         //--- Calculate Hessian
         network.m_weights=wbase.ToVector()+wt.MatMul(hmod.Transpose()+0);
         //--- function call
         CMLPBase::MLPHessianBatch(network,xy,npoints,e,g,h);
         v=network.m_weights.Dot(network.m_weights);
         //--- change value
         e+=0.5*decay*v;
         g+=network.m_weights*decay+0;
         h.Diag(h.Diag()+decay);
         rep.m_nhess++;
         //--- Update lambda
         lambdav=lambdav*lambdadown;
         nu=2;
        }
      //--- update WBest
      v=network.m_weights.Dot(network.m_weights);
      //--- change value
      e=0.5*decay*v+CMLPBase::MLPError(network,xy,npoints);
      //--- check
      if(e<ebest)
        {
         ebest=e;
         wbest=network.m_weights;
        }
     }
//--- copy WBest to output
   network.m_weights=wbest;
  }
//+------------------------------------------------------------------+
//| Neural network training using L-BFGS algorithm with              |
//| regularization. Subroutine trains neural network with restarts   |
//| from random positions. Algorithm is well suited for problems of  |
//| any dimensionality (memory requirements and step complexity are  |
//| linear by weights number).                                       |
//| INPUT PARAMETERS:                                                |
//|     Network    -   neural network with initialized geometry      |
//|     XY         -   training set                                  |
//|     NPoints    -   training set size                             |
//|     Decay      -   weight decay constant, >=0.001                |
//|                    Decay term 'Decay*||Weights||^2' is added to  |
//|                    error function.                               |
//|                    If you don't know what Decay to choose, use   |
//|                    0.001.                                        |
//|     Restarts   -   number of restarts from random position, >0.  |
//|                    If you don't know what Restarts to choose,    |
//|                    use 2.                                        |
//|     WStep      -   stopping criterion. Algorithm stops if step   |
//|                    size is less than WStep. Recommended          |
//|                    value - 0.01. Zero step size means stopping   |
//|                    after MaxIts iterations.                      |
//|     MaxIts     -   stopping criterion. Algorithm stops after     |
//|                    MaxIts iterations (NOT gradient calculations).|
//|                    Zero MaxIts means stopping when step is       |
//|                    sufficiently small.                           |
//| OUTPUT PARAMETERS:                                               |
//|     Network     -   trained neural network.                      |
//|     Info        -   return code:                                 |
//|                     * -8, if both WStep=0 and MaxIts=0           |
//|                     * -2, if there is a point with class number  |
//|                           outside of [0..NOut-1].                |
//|                     * -1, if wrong parameters specified          |
//|                           (NPoints<0, Restarts<1).               |
//|                     *  2, if task has been solved.               |
//|     Rep         -   training report                              |
//+------------------------------------------------------------------+
void CMLPTrain::MLPTrainLBFGS(CMultilayerPerceptron &network,
                              CMatrixDouble &xy,const int npoints,
                              double decay,const int restarts,
                              const double wstep,int maxits,
                              int &info,CMLPReport &rep)
  {
//--- create variables
   int    nin=0;
   int    nout=0;
   int    wcount=0;
   double e=0;
   double v=0;
   double ebest=0;
//--- creating arrays
   CRowDouble w;
   CRowDouble wbest;
//--- create objects of classes
   CMinLBFGSReport internalrep;
   CMinLBFGSState  state;
//--- initialization
   info=0;
//--- Test inputs,parse flags,read network geometry
   if(wstep==0.0 && maxits==0)
     {
      info=-8;
      return;
     }
//--- check
   if(npoints<=0 || restarts<1 || wstep<0.0 || maxits<0)
     {
      info=-1;
      return;
     }
//--- function call
   CMLPBase::MLPProperties(network,nin,nout,wcount);
//--- check
   if(CMLPBase::MLPIsSoftMax(network))
     {
      w=xy.Col(nin)+0;
      //--- check
      if((int)MathRound(w.Min())<0 || (int)MathRound(w.Max())>=nout)
        {
         info=-2;
         return;
        }
     }
//--- change values
   decay=MathMax(decay,m_mindecay);
   info=2;
//--- Prepare
   CMLPBase::MLPInitPreprocessor(network,xy,npoints);
//--- allocation
   wbest.Resize(wcount);
//--- initialization
   ebest=CMath::m_maxrealnumber;
//--- Multiple starts
   rep.m_ncholesky=0;
   rep.m_nhess=0;
   rep.m_ngrad=0;
   for(int pass=1; pass<=restarts; pass++)
     {
      //--- Process
      CMLPBase::MLPRandomize(network);
      w=network.m_weights;
      //--- function calls
      CMinLBFGS::MinLBFGSCreate(wcount,(int)(MathMin(wcount,10)),w,state);
      CMinLBFGS::MinLBFGSSetCond(state,0.0,0.0,wstep,maxits);
      while(CMinLBFGS::MinLBFGSIteration(state))
        {
         network.m_weights=state.m_x;
         //--- function call
         CMLPBase::MLPGradNBatch(network,xy,npoints,state.m_f,state.m_g);
         v=network.m_weights.Dot(network.m_weights);
         state.m_f=state.m_f+0.5*decay*v;
         state.m_g+=network.m_weights*decay+0;
         rep.m_ngrad++;
        }
      //--- function call
      CMinLBFGS::MinLBFGSResults(state,w,internalrep);
      network.m_weights=w;
      //--- Compare with best
      v=network.m_weights.Dot(network.m_weights);
      //--- change value
      e=CMLPBase::MLPErrorN(network,xy,npoints)+0.5*decay*v;
      //--- check
      if(e<ebest)
        {
         wbest=network.m_weights;
         ebest=e;
        }
     }
//--- The best network
   network.m_weights=wbest;
  }
//+------------------------------------------------------------------+
//| Neural network training using early stopping (base algorithm -   |
//| L-BFGS with regularization).                                     |
//| INPUT PARAMETERS:                                                |
//|     Network     -   neural network with initialized geometry     |
//|     TrnXY       -   training set                                 |
//|     TrnSize     -   training set size                            |
//|     ValXY       -   validation set                               |
//|     ValSize     -   validation set size                          |
//|     Decay       -   weight decay constant, >=0.001               |
//|                     Decay term 'Decay*||Weights||^2' is added to |
//|                     error function.                              |
//|                     If you don't know what Decay to choose, use  |
//|                     0.001.                                       |
//|     Restarts    -   number of restarts from random position, >0. |
//|                     If you don't know what Restarts to choose,   |
//|                     use 2.                                       |
//| OUTPUT PARAMETERS:                                               |
//|     Network     -   trained neural network.                      |
//|     Info        -   return code:                                 |
//|                     * -2, if there is a point with class number  |
//|                           outside of [0..NOut-1].                |
//|                     * -1, if wrong parameters specified          |
//|                           (NPoints<0, Restarts<1, ...).          |
//|                     *  2, task has been solved, stopping         |
//|                           criterion met - sufficiently small     |
//|                           step size. Not expected (we use EARLY  |
//|                           stopping) but possible and not an error|
//|                     *  6, task has been solved, stopping         |
//|                           criterion  met - increasing of         |
//|                           validation set error.                  |
//|     Rep         -   training report                              |
//| NOTE:                                                            |
//| Algorithm stops if validation set error increases for a long     |
//| enough or step size is small enought (there are task where       |
//| validation set may decrease for eternity). In any case solution  |
//| returned corresponds to the minimum of validation set error.     |
//+------------------------------------------------------------------+
void CMLPTrain::MLPTrainES(CMultilayerPerceptron &network,
                           CMatrixDouble &trnxy,const int trnsize,
                           CMatrixDouble &valxy,const int valsize,
                           const double decay,int restarts,
                           int &info,CMLPReport &rep)
  {
//--- create variables
   int    nin=0;
   int    nout=0;
   int    wcount=0;
   double e=0;
   double v=0;
   double ebest=0;
   int    itbest=0;
   int    itcnt=0;
   double wstep=0;
   bool   needrandomization=true;
//--- creating arrays
   CRowDouble w;
   CRowDouble wbest;
   CRowDouble wfinal;
   double efinal=0;
//--- objects of classes
   CMinLBFGSReport internalrep;
   CMinLBFGSState state;
//--- initialization
   info=0;
   wstep=0.001;
//--- Test inputs,parse flags,read network geometry
   if(trnsize<=0 || valsize<=0 || restarts<1 || decay<0.0)
     {
      info=-1;
      return;
     }
   if(restarts==-1)
     {
      needrandomization=false;
      restarts=1;
     }
//--- function call
   CMLPBase::MLPProperties(network,nin,nout,wcount);
//--- check
   if(CMLPBase::MLPIsSoftMax(network))
     {
      w=trnxy.Col(nin)+0;
      w.Resize(trnsize);
      //--- check
      if((int)MathRound(w.Min())<0 || (int)MathRound(w.Max())>=nout)
        {
         info=-2;
         return;
        }
      w=valxy.Col(nin)+0;
      w.Resize(valsize);
      //--- check
      if((int)MathRound(w.Min())<0 || (int)MathRound(w.Max())>=nout)
        {
         info=-2;
         return;
        }
     }
//--- change value
   info=2;
//--- Prepare
   CMLPBase::MLPInitPreprocessor(network,trnxy,trnsize);
//--- initialization
   wfinal=vector<double>::Zeros(wcount);
   efinal=CMath::m_maxrealnumber;
//--- Multiple starts
   rep.m_ncholesky=0;
   rep.m_nhess=0;
   rep.m_ngrad=0;
//--- calculation
   for(int pass=1; pass<=restarts; pass++)
     {
      //--- Process
      if(needrandomization)
         CMLPBase::MLPRandomize(network);
      //--- change values
      ebest=CMLPBase::MLPError(network,valxy,valsize);
      wbest=network.m_weights;
      //--- change values
      itbest=0;
      w=network.m_weights;
      //--- function calls
      CMinLBFGS::MinLBFGSCreate(wcount,(int)(MathMin(wcount,10)),w,state);
      CMinLBFGS::MinLBFGSSetCond(state,0.0,0.0,wstep,0);
      CMinLBFGS::MinLBFGSSetXRep(state,true);
      while(CMinLBFGS::MinLBFGSIteration(state))
        {
         //--- Calculate gradient
         if(state.m_needfg)
           {
            network.m_weights=state.m_x;
            //--- function call
            CMLPBase::MLPGradNBatch(network,trnxy,trnsize,state.m_f,state.m_g);
            v=network.m_weights.Dot(network.m_weights);
            state.m_f+=0.5*decay*v;
            state.m_g+=network.m_weights*decay+0;
            rep.m_ngrad++;
           }
         //--- Validation set
         if(state.m_xupdated)
           {
            network.m_weights=state.m_x;
            //--- function call
            e=CMLPBase::MLPError(network,valxy,valsize);
            //--- check
            if(e<ebest)
              {
               ebest=e;
               wbest=network.m_weights;
               itbest=internalrep.m_iterationscount;
              }
            //--- check
            if(itcnt>30 && (double)(itcnt)>(double)(1.5*itbest))
              {
               info=6;
               break;
              }
            itcnt++;
           }
        }
      //--- function call
      CMinLBFGS::MinLBFGSResults(state,w,internalrep);
      //--- Compare with final answer
      if(ebest<efinal)
        {
         wfinal=wbest;
         efinal=ebest;
        }
     }
//--- The best network
   network.m_weights=wfinal;
  }
//+------------------------------------------------------------------+
//| Cross-validation estimate of generalization error.               |
//| Base algorithm - L-BFGS.                                         |
//| INPUT PARAMETERS:                                                |
//|     Network     -   neural network with initialized geometry.    |
//|                     Network is not changed during                |
//|                     cross-validation - it is used only as a      |
//|                     representative of its architecture.          |
//|     XY          -   training set.                                |
//|     SSize       -   training set size                            |
//|     Decay       -   weight  decay, same as in MLPTrainLBFGS      |
//|     Restarts    -   number of restarts, >0.                      |
//|                     restarts are counted for each partition      |
//|                     separately, so total number of restarts will |
//|                     be Restarts*FoldsCount.                      |
//|     WStep       -   stopping criterion, same as in MLPTrainLBFGS |
//|     MaxIts      -   stopping criterion, same as in MLPTrainLBFGS |
//|     FoldsCount  -   number of folds in k-fold cross-validation,  |
//|                     2<=FoldsCount<=SSize.                        |
//|                     recommended value: 10.                       |
//| OUTPUT PARAMETERS:                                               |
//|     Info        -   return code, same as in MLPTrainLBFGS        |
//|     Rep         -   report, same as in MLPTrainLM/MLPTrainLBFGS  |
//|     CVRep       -   generalization error estimates               |
//+------------------------------------------------------------------+
void CMLPTrain::MLPKFoldCVLBFGS(CMultilayerPerceptron &network,
                                CMatrixDouble &xy,const int npoints,
                                const double decay,const int restarts,
                                const double wstep,const int maxits,
                                const int foldscount,int &info,
                                CMLPReport &rep,CMLPCVReport &cvrep)
  {
//--- initialization
   info=0;
//--- function call
   MLPKFoldCVGeneral(network,xy,npoints,decay,restarts,foldscount,false,wstep,maxits,info,rep,cvrep);
  }
//+------------------------------------------------------------------+
//| Cross-validation estimate of generalization error.               |
//| Base algorithm - Levenberg-Marquardt.                            |
//| INPUT PARAMETERS:                                                |
//|     Network     -   neural network with initialized geometry.    |
//|                     Network is not changed during                |
//|                     cross-validation - it is used only as a      |
//|                     representative of its architecture.          |
//|     XY          -   training set.                                |
//|     SSize       -   training set size                            |
//|     Decay       -   weight  decay, same as in MLPTrainLBFGS      |
//|     Restarts    -   number of restarts, >0.                      |
//|                     restarts are counted for each partition      |
//|                     separately, so total number of restarts will |
//|                     be Restarts*FoldsCount.                      |
//|     FoldsCount  -   number of folds in k-fold cross-validation,  |
//|                     2<=FoldsCount<=SSize.                        |
//|                     recommended value: 10.                       |
//| OUTPUT PARAMETERS:                                               |
//|     Info        -   return code, same as in MLPTrainLBFGS        |
//|     Rep         -   report, same as in MLPTrainLM/MLPTrainLBFGS  |
//|     CVRep       -   generalization error estimates               |
//+------------------------------------------------------------------+
void CMLPTrain::MLPKFoldCVLM(CMultilayerPerceptron &network,
                             CMatrixDouble &xy,const int npoints,
                             const double decay,const int restarts,
                             int foldscount,int &info,CMLPReport &rep,
                             CMLPCVReport &cvrep)
  {
//--- initialization
   info=0;
//--- function call
   MLPKFoldCVGeneral(network,xy,npoints,decay,restarts,foldscount,true,0.0,0,info,rep,cvrep);
  }
//+------------------------------------------------------------------+
//| Creation of the network trainer object for regression networks   |
//| INPUT PARAMETERS:                                                |
//|   NIn         -  number of inputs, NIn>=1                        |
//|   NOut        -  number of outputs, NOut>=1                      |
//| OUTPUT PARAMETERS:                                               |
//|   S           -  neural network trainer object.                  |
//| This structure can be used to train any regression network with  |
//| NIn inputs and NOut outputs.                                     |
//+------------------------------------------------------------------+
void CMLPTrain::MLPCreateTrainer(int nin,int nout,CMLPTrainer &s)
  {
//--- check
   if(!CAp::Assert(nin>=1,__FUNCTION__+": NIn<1."))
      return;
   if(!CAp::Assert(nout>=1,__FUNCTION__+": NOut<1."))
      return;
//--- change value
   s.m_nin=nin;
   s.m_nout=nout;
   s.m_rcpar=true;
   s.m_lbfgsfactor=m_defaultlbfgsfactor;
   s.m_decay=1.0E-6;
   MLPSetCond(s,0,0);
   s.m_datatype=0;
   s.m_npoints=0;
   MLPSetAlgoBatch(s);
  }
//+------------------------------------------------------------------+
//| Creation of the network trainer object for classification        |
//| networks                                                         |
//| INPUT PARAMETERS:                                                |
//|   NIn         -  number of inputs, NIn>=1                        |
//|   NClasses    -  number of classes, NClasses>=2                  |
//| OUTPUT PARAMETERS:                                               |
//|   S           -  neural network trainer object.                  |
//| This structure can be used to train any classification network   |
//| with NIn inputs and NOut outputs.                                |
//+------------------------------------------------------------------+
void CMLPTrain::MLPCreateTrainerCls(int nin,int nclasses,CMLPTrainer &s)
  {
//--- check
   if(!CAp::Assert(nin>=1,__FUNCTION__+": NIn<1."))
      return;
   if(!CAp::Assert(nclasses>=2,__FUNCTION__+": NClasses<2."))
      return;

   s.m_nin=nin;
   s.m_nout=nclasses;
   s.m_rcpar=false;
   s.m_lbfgsfactor=m_defaultlbfgsfactor;
   s.m_decay=1.0E-6;
   MLPSetCond(s,0,0);
   s.m_datatype=0;
   s.m_npoints=0;
   MLPSetAlgoBatch(s);
  }
//+------------------------------------------------------------------+
//| This function sets "current dataset" of the trainer object to one|
//| passed by user.                                                  |
//| INPUT PARAMETERS:                                                |
//|   S           -  trainer object                                  |
//|   XY          -  training set, see below for information on the  |
//|                  training set format. This function checks       |
//|                  correctness of the dataset (no NANs/INFs, class |
//|                  numbers are correct) and throws exception when  |
//|                  incorrect dataset is passed.                    |
//|   NPoints     -  points count, >=0.                              |
//| DATASET FORMAT:                                                  |
//|   This function uses two different dataset formats - one for     |
//|   regression networks, another one for classification networks.  |
//| For regression networks with NIn inputs and NOut outputs         |
//| following dataset format is used:                                |
//|      * dataset is given by NPoints*(NIn+NOut) matrix             |
//|      * each row corresponds to one example                       |
//|      * first NIn columns are inputs, next NOut columns are       |
//|        outputs                                                   |
//| For classification networks with NIn inputs and NClasses clases  |
//| following datasetformat is used:                                 |
//|      * dataset is given by NPoints*(NIn+1) matrix                |
//|      * each row corresponds to one example                       |
//|      * first NIn columns are inputs, last column stores class    |
//|        number (from 0 to NClasses-1).                            |
//+------------------------------------------------------------------+
void CMLPTrain::MLPSetDataset(CMLPTrainer &s,CMatrixDouble &xy,int npoints)
  {
   int ndim=0;
//--- check
   if(!CAp::Assert(s.m_nin>=1,__FUNCTION__+": possible parameter S is not initialized or spoiled(S.NIn<=0)."))
      return;
   if(!CAp::Assert(npoints>=0,__FUNCTION__+": NPoint<0"))
      return;
   if(!CAp::Assert(npoints<=xy.Rows(),__FUNCTION__+": invalid size of matrix XY(NPoint more then rows of matrix XY)"))
      return;
//--- change value
   s.m_datatype=0;
   s.m_npoints=npoints;
   if(npoints==0)
      return;
   if(s.m_rcpar)
     {
      //--- check
      if(!CAp::Assert(s.m_nout>=1,__FUNCTION__+": possible parameter S is not initialized or is spoiled(NOut<1 for regression)."))
         return;
      ndim=s.m_nin+s.m_nout;
      //--- check
      if(!CAp::Assert(ndim<=xy.Cols(),__FUNCTION__+": invalid size of matrix XY(too few columns in matrix XY)."))
         return;
      if(!CAp::Assert(CApServ::IsFiniteMatrix(xy,npoints,ndim),__FUNCTION__+": parameter XY contains Infinite or NaN."))
         return;
     }
   else
     {
      //--- check
      if(!CAp::Assert(s.m_nout>=2,__FUNCTION__+": possible parameter S is not initialized or is spoiled(NClasses<2 for classifier)."))
         return;
      ndim=s.m_nin+1;
      //--- check
      if(!CAp::Assert(ndim<=xy.Cols(),__FUNCTION__+": invalid size of matrix XY(too few columns in matrix XY)."))
         return;
      if(!CAp::Assert(CApServ::IsFiniteMatrix(xy,npoints,ndim),__FUNCTION__+": parameter XY contains Infinite or NaN."))
         return;
      vector<double> temp=xy.Col(s.m_nin);
      temp.Resize(npoints);
      //--- check
      if(!CAp::Assert((int)MathRound(temp.Min())>=0 && (int)MathRound(temp.Max())<s.m_nout,__FUNCTION__+": invalid parameter XY(in classifier used nonexistent class number: either XY[.,NIn]<0 or XY[.,NIn]>=NClasses)."))
         return;
     }
   s.m_densexy=xy;
   s.m_densexy.Resize(npoints,ndim);
  }
//+------------------------------------------------------------------+
//| This function sets "current dataset" of the trainer object to one|
//| passed by user (sparse matrix is used to store dataset).         |
//| INPUT PARAMETERS:                                                |
//|   S        -  trainer object                                     |
//|   XY       -  training set, see below for information on the     |
//|               training set format. This function checks          |
//|               correctness of the dataset (no NANs/INFs, class    |
//|               numbers are correct) and throws exception when     |
//|               incorrect dataset is passed. Any sparse storage    |
//|               format can be used: Hash-table, CRS...             |
//|   NPoints  -  points count, >=0                                  |
//| DATASET FORMAT:                                                  |
//|   This function uses two different dataset formats - one for     |
//|   regression networks, another one for classification networks.  |
//| For regression networks with NIn inputs and NOut outputs         |
//| following dataset format is used:                                |
//|   * dataset is given by NPoints*(NIn+NOut) matrix                |
//|   * each row corresponds to one example                          |
//|   * first NIn columns are inputs, next NOut columns are outputs  |
//| For classification networks with NIn inputs and NClasses clases  |
//| following datasetformat is used:                                 |
//|   * dataset is given by NPoints*(NIn+1) matrix                   |
//|   * each row corresponds to one example                          |
//|   * first NIn columns are inputs, last column stores class number|
//|     (from 0 to NClasses-1).                                      |
//+------------------------------------------------------------------+
void CMLPTrain::MLPSetSparseDataset(CMLPTrainer &s,CSparseMatrix &xy,int npoints)
  {
//--- create variables
   double v=0;
   int    t0=0;
   int    t1=0;
   int    i=0;
   int    j=0;
//--- Check correctness of the data
   if(!CAp::Assert(s.m_nin>0,__FUNCTION__+": possible parameter S is not initialized or spoiled(S.NIn<=0)."))
      return;
   if(!CAp::Assert(npoints>=0,__FUNCTION__+": NPoint<0"))
      return;
   if(!CAp::Assert(npoints<=CSparse::SparseGetNRows(xy),__FUNCTION__+": invalid size of sparse matrix XY(NPoint more then rows of matrix XY)"))
      return;
   if(npoints>0)
     {
      t0=0;
      t1=0;
      if(s.m_rcpar)
        {
         if(!CAp::Assert(s.m_nout>=1,__FUNCTION__+": possible parameter S is not initialized or is spoiled(NOut<1 for regression)."))
            return;
         if(!CAp::Assert(s.m_nin+s.m_nout<=CSparse::SparseGetNCols(xy),__FUNCTION__+": invalid size of sparse matrix XY(too few columns in sparse matrix XY)."))
            return;
         //---
         while(CSparse::SparseEnumerate(xy,t0,t1,i,j,v))
           {
            if(i<npoints && j<s.m_nin+s.m_nout)
               if(!CAp::Assert(MathIsValidNumber(v),__FUNCTION__+": sparse matrix XY contains Infinite or NaN."))
                  return;
           }
        }
      else
        {
         if(!CAp::Assert(s.m_nout>=2,__FUNCTION__+": possible parameter S is not initialized or is spoiled(NClasses<2 for classifier)."))
            return;
         if(!CAp::Assert(s.m_nin+1<=CSparse::SparseGetNCols(xy),__FUNCTION__+": invalid size of sparse matrix XY(too few columns in sparse matrix XY)."))
            return;
         //---
         while(CSparse::SparseEnumerate(xy,t0,t1,i,j,v))
           {
            if(i<npoints && j<=s.m_nin)
              {
               if(j!=s.m_nin)
                 {
                  if(!CAp::Assert(MathIsValidNumber(v),__FUNCTION__+": sparse matrix XY contains Infinite or NaN."))
                     return;
                 }
               else
                 {
                  if(!CAp::Assert((MathIsValidNumber(v) && (int)MathRound(v)>=0) && (int)MathRound(v)<s.m_nout,__FUNCTION__+": invalid sparse matrix XY(in classifier used nonexistent class number: either XY[.,NIn]<0 or XY[.,NIn]>=NClasses)."))
                     return;
                 }
              }
           }
        }
     }
//--- Set dataset
   s.m_datatype=1;
   s.m_npoints=npoints;
   CSparse::SparseCopyToCRS(xy,s.m_sparsexy);
  }
//+------------------------------------------------------------------+
//| This function sets weight decay coefficient which is used for    |
//| training.                                                        |
//| INPUT PARAMETERS:                                                |
//|   S        -  trainer object                                     |
//|   Decay    -  weight decay coefficient, >=0. Weight decay term   |
//|               'Decay*||Weights||^2' is added to error function.  |
//|               If you don't know what Decay to choose, use 1.0E-3.|
//|               Weight decay can be set to zero, in this case      |
//|               network is trained without weight decay.           |
//| NOTE: by default network uses some small nonzero value for weight|
//| decay.                                                           |
//+------------------------------------------------------------------+
void CMLPTrain::MLPSetDecay(CMLPTrainer &s,double decay)
  {
//--- check
   if(!CAp::Assert(MathIsValidNumber(decay),__FUNCTION__+": parameter Decay contains Infinite or NaN."))
      return;
   if(!CAp::Assert(decay>=0.0,__FUNCTION__+": Decay<0."))
      return;
   s.m_decay=decay;
  }
//+------------------------------------------------------------------+
//| This function sets stopping criteria for the optimizer.          |
//| INPUT PARAMETERS:                                                |
//|   S        -  trainer object                                     |
//|   WStep    -  stopping criterion. Algorithm stops if step size is|
//|               less than WStep. Recommended value - 0.01. Zero    |
//|               step size means stopping after MaxIts iterations.  |
//|               WStep>=0.                                          |
//|   MaxIts   -  stopping criterion. Algorithm stops after MaxIts   |
//|               epochs (full passes over entire dataset). Zero     |
//|               MaxIts means stopping when step is sufficiently    |
//|               small. MaxIts>=0.                                  |
//| NOTE: by default, WStep=0.005 and MaxIts=0 are used. These values|
//|       are also used when MLPSetCond() is called with WStep=0 and |
//|       MaxIts=0.                                                  |
//| NOTE: these stopping criteria are used for all kinds of neural   |
//|       training-from "conventional" networks to early stopping  |
//|       ensembles. When used for "conventional" networks, they are |
//|       used as the only stopping criteria. When combined with     |
//|       early stopping, they used as ADDITIONAL stopping criteria  |
//|       which can terminate early stopping algorithm.              |
//+------------------------------------------------------------------+
void CMLPTrain::MLPSetCond(CMLPTrainer &s,double wstep,int maxits)
  {
//--- check
   if(!CAp::Assert(MathIsValidNumber(wstep),__FUNCTION__+": parameter WStep contains Infinite or NaN."))
      return;
   if(!CAp::Assert(wstep>=0.0,__FUNCTION__+": WStep<0."))
      return;
   if(!CAp::Assert(maxits>=0,__FUNCTION__+": MaxIts<0."))
      return;

   if(wstep!=0.0 || maxits!=0)
     {
      s.m_wstep=wstep;
      s.m_maxits=maxits;
     }
   else
     {
      s.m_wstep=0.005;
      s.m_maxits=0;
     }
  }
//+------------------------------------------------------------------+
//| This function sets training algorithm: batch training using      |
//| L-BFGS will be used.                                             |
//| This algorithm:                                                  |
//|   * the most robust for small-scale problems, but may be too slow|
//|     for large scale ones.                                        |
//|   * perfoms full pass through the dataset before performing step |
//|   * uses conditions specified by MLPSetCond() for stopping       |
//|   * is default one used by trainer object                        |
//| INPUT PARAMETERS:                                                |
//|   S        -  trainer object                                     |
//+------------------------------------------------------------------+
void CMLPTrain::MLPSetAlgoBatch(CMLPTrainer &s)
  {
   s.m_algokind=0;
  }
//+------------------------------------------------------------------+
//| This function trains neural network passed to this function,     |
//| using current dataset (one which was passed to MLPSetDataset()   |
//| or MLPSetSparseDataset()) and current training settings. Training|
//| from NRestarts random starting positions is performed, best      |
//| network is chosen.                                               |
//| Training is performed using current training algorithm.          |
//| INPUT PARAMETERS:                                                |
//|   S        -  trainer object                                     |
//|   Network  -  neural network. It must have same number of inputs |
//|               and output/classes as was specified during creation|
//|               of the trainer object.                             |
//|   NRestarts-  number of restarts, >=0:                           |
//|               * NRestarts>0 means that specified number of random|
//|                 restarts are performed, best network is chosen   |
//|                 after training                                   |
//|               * NRestarts=0 means that current state of the      |
//|                 network is used for training.                    |
//| OUTPUT PARAMETERS:                                               |
//|   Network  -  trained network                                    |
//| NOTE: when no dataset was specified with MLPSetDataset /         |
//|       SetSparseDataset(), network is filled by zero values. Same |
//|       behavior for functions MLPStartTraining and                |
//|       MLPContinueTraining.                                       |
//| NOTE: this method uses sum-of-squares error function for training|
//+------------------------------------------------------------------+
void CMLPTrain::MLPTrainNetwork(CMLPTrainer &s,CMultilayerPerceptron &network,
                                int nrestarts,CMLPReport &rep)
  {
//--- create variables
   int nin=0;
   int nout=0;
   int wcount=0;
   int ntype=0;
   int ttype=0;
//--- check
   if(!CAp::Assert(s.m_npoints>=0,__FUNCTION__+": parameter S is not initialized or is spoiled(S.NPoints<0)"))
      return;
   if(!CMLPBase::MLPIsSoftMax(network))
      ntype=0;
   else
      ntype=1;
   if(s.m_rcpar)
      ttype=0;
   else
      ttype=1;
//--- check
   if(!CAp::Assert(ntype==ttype,__FUNCTION__+": type of input network is not similar to network type in trainer object"))
      return;
   CMLPBase::MLPProperties(network,nin,nout,wcount);
//--- check
   if(!CAp::Assert(s.m_nin==nin,__FUNCTION__+": number of inputs in trainer is not equal to number of inputs in network"))
      return;
   if(!CAp::Assert(s.m_nout==nout,__FUNCTION__+": number of outputs in trainer is not equal to number of outputs in network"))
      return;
   if(!CAp::Assert(nrestarts>=0,__FUNCTION__+": NRestarts<0."))
      return;
//--- Train
   MLPTrainNetworkX(s,nrestarts,-1,s.m_subset,-1,s.m_subset,0,network,rep);
  }
//+------------------------------------------------------------------+
//| IMPORTANT: this is an "expert" version of the MLPTrain() function|
//|            We do not recommend you to use it unless you are      |
//|            pretty sure that you need ability to monitor training |
//|            progress.                                             |
//| This function performs step-by-step training of the neural       |
//| network. Here "step-by-step" means that training starts with     |
//| MLPStartTraining() call, and then user subsequently calls        |
//| MLPContinueTraining() to perform one more iteration of the       |
//| training.                                                        |
//| After call to this function trainer object remembers network and |
//| is ready to train it. However, no training is performed until    |
//| first call to MLPContinueTraining() function. Subsequent calls   |
//| to MLPContinueTraining() will advance training progress one      |
//| iteration further.                                               |
//| EXAMPLE:                                                         |
//|   >                                                              |
//|   > ...initialize network and trainer object....                 |
//|   >                                                              |
//|   > MLPStartTraining(Trainer, Network, True)                     |
//|   > while MLPContinueTraining(Trainer, Network) do               |
//|   >     ...visualize training progress...                        |
//|   >                                                              |
//| INPUT PARAMETERS:                                                |
//|   S        -  trainer object                                     |
//|   Network  -  neural network. It must have same number of inputs |
//|               and output/classes as was specified during creation|
//|               of the trainer object.                             |
//|   RandomStart -  randomize network before training or not:       |
//|               * True means that network is randomized and its    |
//|                 initial state (one which was passed to the       |
//|                 trainer object) is lost.                         |
//|               * False means that training is started from the    |
//|                 current state of the network                     |
//| OUTPUT PARAMETERS:                                               |
//|   Network  -  neural network which is ready to training (weights |
//|               are initialized, preprocessor is initialized using |
//|               current training set)                              |
//| NOTE: this method uses sum-of-squares error function for training|
//| NOTE: it is expected that trainer object settings are NOT changed|
//|       during step-by-step training, i.e. no one changes stopping |
//|       criteria or training set during training. It is possible   |
//|       and there is no defense against such actions, but algorithm|
//|       behavior in such cases is undefined and can be             |
//|       unpredictable.                                             |
//+------------------------------------------------------------------+
void CMLPTrain::MLPStartTraining(CMLPTrainer &s,CMultilayerPerceptron &network,
                                 bool randomstart)
  {
//--- create variables
   int nin=0;
   int nout=0;
   int wcount=0;
   int ntype=0;
   int ttype=0;
//--- check
   if(!CAp::Assert(s.m_npoints>=0,__FUNCTION__+": parameter S is not initialized or is spoiled(S.NPoints<0)"))
      return;
   if(!CMLPBase::MLPIsSoftMax(network))
      ntype=0;
   else
      ntype=1;
   if(s.m_rcpar)
      ttype=0;
   else
      ttype=1;
//--- check
   if(!CAp::Assert(ntype==ttype,__FUNCTION__+": type of input network is not similar to network type in trainer object"))
      return;
   CMLPBase::MLPProperties(network,nin,nout,wcount);
//--- check
   if(!CAp::Assert(s.m_nin==nin,__FUNCTION__+": number of inputs in trainer is not equal to number of inputs in the network."))
      return;
   if(!CAp::Assert(s.m_nout==nout,__FUNCTION__+": number of outputs in trainer is not equal to number of outputs in the network."))
      return;
//--- Initialize temporaries
   InitMLPTrnSession(network,randomstart,s,s.m_session);
//--- Train network
   MLPStartTrainingX(s,randomstart,-1,s.m_subset,-1,s.m_session);
//--- Update network
   CMLPBase::MLPCopyTunableParameters(s.m_session.m_network,network);
  }
//+------------------------------------------------------------------+
//| IMPORTANT: this is an "expert" version of the MLPTrain() function|
//|            We do not recommend you to use it unless you are      |
//|            pretty sure that you need ability to monitor training |
//|            progress.                                             |
//| This function performs step-by-step training of the neural       |
//| network. Here "step-by-step" means that training starts with     |
//| MLPStartTraining() call, and then user subsequently calls        |
//| MLPContinueTraining() to perform one more iteration of the       |
//| training.                                                        |
//| This function performs one more iteration of the training and    |
//| returns either True (training continues) or False (training      |
//| stopped). In case True was returned, Network weights are updated |
//| according to the  current  state of the optimization progress.   |
//| In case False was returned, no additional updates is performed   |
//| (previous update of the network weights moved us to the final    |
//| point, and no additional updates is needed).                     |
//| EXAMPLE:                                                         |
//|   >                                                              |
//|   > [initialize network and trainer object]                      |
//|   >                                                              |
//|   > MLPStartTraining(Trainer, Network, True)                     |
//|   > while MLPContinueTraining(Trainer, Network) do               |
//|   >     [visualize training progress]                            |
//|   >                                                              |
//| INPUT PARAMETERS:                                                |
//|   S        -  trainer object                                     |
//|   Network  -  neural network structure, which is used to store   |
//|               current state of the training process.             |
//| OUTPUT PARAMETERS:                                               |
//|   Network  -  weights of the neural network are rewritten by the |
//|               current approximation.                             |
//| NOTE: this method uses sum-of-squares error function for training|
//| NOTE: it is expected that trainer object settings are NOT changed|
//|       during step-by-step training, i.e. no one changes stopping |
//|       criteria or training set during training. It is possible   |
//|       and there is no defense against such actions, but algorithm|
//|       behavior in such cases is undefined and can be             |
//|       unpredictable.                                             |
//| NOTE: It is expected that Network is the same one which was      |
//|       passed to MLPStartTraining() function. However, THIS       |
//|       function checks only following:                            |
//|         * that number of network inputs is consistent with       |
//|           trainer object settings                                |
//|         * that number of network outputs / classes is consistent |
//|           with trainer object settings                           |
//|         * that number of network weights is the same as number of|
//|           weights in the network passed to MLPStartTraining()    |
//|           function Exception is thrown when these conditions are |
//|           violated.                                              |
//| It is also expected that you do not change state of the network  |
//| on your own - the only party who has right to change network     |
//| during its training is a trainer object. Any attempt to interfere|
//| with trainer may lead to unpredictable results.                  |
//+------------------------------------------------------------------+
bool CMLPTrain::MLPContinueTraining(CMLPTrainer &s,
                                    CMultilayerPerceptron &network)
  {
//--- create variables
   bool result;
   int  nin=0;
   int  nout=0;
   int  wcount=0;
   int  ntype=0;
   int  ttype=0;
   int  i_=0;
//--- check
   if(!CAp::Assert(s.m_npoints>=0,__FUNCTION__+": parameter S is not initialized or is spoiled(S.NPoints<0)"))
      return(false);
   if(s.m_rcpar)
      ttype=0;
   else
      ttype=1;
   if(!CMLPBase::MLPIsSoftMax(network))
      ntype=0;
   else
      ntype=1;
//--- check
   if(!CAp::Assert(ntype==ttype,__FUNCTION__+": type of input network is not similar to network type in trainer object."))
      return(false);
   CMLPBase::MLPProperties(network,nin,nout,wcount);
//--- check
   if(!CAp::Assert(s.m_nin==nin,__FUNCTION__+": number of inputs in trainer is not equal to number of inputs in the network."))
      return(false);
   if(!CAp::Assert(s.m_nout==nout,__FUNCTION__+": number of outputs in trainer is not equal to number of outputs in the network."))
      return(false);
   result=MLPContinueTrainingX(s,s.m_subset,-1,s.m_ngradbatch,s.m_session);
   if(result)
      network.m_weights=s.m_session.m_network.m_weights;
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| Internal cross-validation subroutine                             |
//+------------------------------------------------------------------+
void CMLPTrain::MLPKFoldCVGeneral(CMultilayerPerceptron &n,
                                  CMatrixDouble &xy,const int npoints,
                                  const double decay,const int restarts,
                                  const int foldscount,const bool lmalgorithm,
                                  const double wstep,const int maxits,
                                  int &info,CMLPReport &rep,
                                  CMLPCVReport &cvrep)
  {
//--- create variables
   int k=0;
   int nin=0;
   int nout=0;
   int rowlen=0;
   int wcount=0;
   int nclasses=0;
   int tssize=0;
   int cvssize=0;
   int relcnt=0;
//--- creating arrays
   CRowInt folds;
   CRowDouble x;
   CRowDouble y;
//--- create matrix
   CMatrixDouble cvset;
   CMatrixDouble testset;
//--- creating arrays
   CMultilayerPerceptron network;
   CMLPReport            internalrep;
//--- initialization
   info=0;
//--- Read network geometry,test parameters
   CMLPBase::MLPProperties(n,nin,nout,wcount);
//--- check
   if(CMLPBase::MLPIsSoftMax(n))
     {
      nclasses=nout;
      rowlen=nin+1;
     }
   else
     {
      nclasses=-nout;
      rowlen=nin+nout;
     }
//--- check
   if(npoints<=0 || foldscount<2 || foldscount>npoints)
     {
      info=-1;
      return;
     }
//--- function call
   CMLPBase::MLPCopy(n,network);
//--- K-fold out cross-validation.
//--- First,estimate generalization error
   testset.Resize(npoints,rowlen);
   cvset.Resize(npoints,rowlen);
   x.Resize(nin);
   y.Resize(nout);
//--- function call
   MLPKFoldSplit(xy,npoints,nclasses,foldscount,false,folds);
//--- change values
   cvrep.m_RelCLSError=0;
   cvrep.m_AvgCE=0;
   cvrep.m_RMSError=0;
   cvrep.m_AvgError=0;
   cvrep.m_AvgRelError=0;
   rep.m_ngrad=0;
   rep.m_nhess=0;
   rep.m_ncholesky=0;
   relcnt=0;
//--- calculation
   for(int fold=0; fold<=foldscount-1; fold++)
     {
      //--- Separate set
      tssize=0;
      cvssize=0;
      for(int i=0; i<npoints; i++)
        {
         //--- check
         if(folds[i]==fold)
           {
            testset.Row(tssize,xy[i]+0);
            tssize++;
           }
         else
           {
            cvset.Row(cvssize,xy[i]+0);
            cvssize++;
           }
        }
      //--- Train on CV training set
      if(lmalgorithm)
         MLPTrainLM(network,cvset,cvssize,decay,restarts,info,internalrep);
      else
         MLPTrainLBFGS(network,cvset,cvssize,decay,restarts,wstep,maxits,info,internalrep);
      //--- check
      if(info<0)
        {
         //--- change values
         cvrep.m_RelCLSError=0;
         cvrep.m_AvgCE=0;
         cvrep.m_RMSError=0;
         cvrep.m_AvgError=0;
         cvrep.m_AvgRelError=0;
         //--- exit the function
         return;
        }
      //--- change values
      rep.m_ngrad+=internalrep.m_ngrad;
      rep.m_nhess+=internalrep.m_nhess;
      rep.m_ncholesky+=internalrep.m_ncholesky;
      //--- Estimate error using CV test set
      if(CMLPBase::MLPIsSoftMax(network))
        {
         //--- classification-only code
         cvrep.m_RelCLSError+=CMLPBase::MLPClsError(network,testset,tssize);
         cvrep.m_AvgCE+=CMLPBase::MLPErrorN(network,testset,tssize);
        }
      //--- calculation
      for(int i=0; i<=tssize-1; i++)
        {
         x=testset[i]+0;
         x.Resize(nin);
         //--- function call
         CMLPBase::MLPProcess(network,x,y);
         //--- check
         if(CMLPBase::MLPIsSoftMax(network))
           {
            //--- Classification-specific code
            k=(int)MathRound(testset.Get(i,nin));
            for(int j=0; j<nout; j++)
              {
               //--- check
               if(j==k)
                 {
                  //--- change values
                  cvrep.m_RMSError+=CMath::Sqr(y[j]-1);
                  cvrep.m_AvgError+=MathAbs(y[j]-1);
                  cvrep.m_AvgRelError+=MathAbs(y[j]-1);
                  relcnt++;
                 }
               else
                 {
                  //--- change values
                  cvrep.m_RMSError+=CMath::Sqr(y[j]);
                  cvrep.m_AvgError+=MathAbs(y[j]);
                 }
              }
           }
         else
           {
            //--- Regression-specific code
            for(int j=0; j<nout; j++)
              {
               cvrep.m_RMSError+=CMath::Sqr(y[j]-testset.Get(i,nin+j));
               cvrep.m_AvgError=cvrep.m_AvgError+MathAbs(y[j]-testset.Get(i,nin+j));
               //--- check
               if(testset.Get(i,nin+j)!=0.0)
                 {
                  cvrep.m_AvgRelError+=MathAbs((y[j]-testset.Get(i,nin+j))/testset.Get(i,nin+j));
                  relcnt++;
                 }
              }
           }
        }
     }
//--- check
   if(CMLPBase::MLPIsSoftMax(network))
     {
      cvrep.m_RelCLSError/=npoints;
      cvrep.m_AvgCE/=(MathLog(2)*npoints);
     }
//--- change values
   cvrep.m_RMSError=MathSqrt(cvrep.m_RMSError/(npoints*nout));
   cvrep.m_AvgError/=(npoints*nout);
   cvrep.m_AvgRelError/=relcnt;
   info=1;
  }
//+------------------------------------------------------------------+
//| Subroutine prepares K-fold split of the training set.            |
//| NOTES:                                                           |
//|     "NClasses>0" means that we have classification task.         |
//|     "NClasses<0" means regression task with -NClasses real       |
//|     outputs.                                                     |
//+------------------------------------------------------------------+
void CMLPTrain::MLPKFoldSplit(CMatrixDouble &xy,const int npoints,
                              const int nclasses,const int foldscount,
                              const bool stratifiedsplits,CRowInt &folds)
  {
//--- create variables
   int j=0;
   CHighQualityRandState rs;
//--- test parameters
   if(!CAp::Assert(npoints>0,__FUNCTION__+": wrong NPoints!"))
      return;
//--- check
   if(!CAp::Assert(nclasses>1 || nclasses<0,__FUNCTION__+": wrong NClasses!"))
      return;
//--- check
   if(!CAp::Assert(foldscount>=2 && foldscount<=npoints,__FUNCTION__+" wrong FoldsCount!"))
      return;
//--- check
   if(!CAp::Assert(!stratifiedsplits,__FUNCTION__+": stratified splits are not supported!"))
      return;
//--- Folds
   folds.Resize(npoints);
   CHighQualityRand::HQRndRandomize(rs);
   for(int i=0; i<npoints; i++)
      folds.Set(i,i*foldscount/npoints);
//--- calculation
   for(int i=0; i<npoints-1; i++)
     {
      j=i+CHighQualityRand::HQRndUniformI(rs,npoints-i);
      //--- check
      if(j!=i)
         folds.Swap(i,j);
     }
  }
//+------------------------------------------------------------------+
//| This function trains neural network passed to this function,     |
//| using current dataset (one which was passed to MLPSetDataset() or|
//| MLPSetSparseDataset()) and current training settings. Training   |
//| from NRestarts random starting positions is performed, best      |
//| network is chosen.                                               |
//| This function is inteded to be used internally. It may be used in|
//| several settings:                                                |
//|  *training with ValSubsetSize=0, corresponds  to  "normal"     |
//|     training  with termination  criteria  based on S.MaxIts      |
//|     (steps count) and S.WStep (step size). Training sample is    |
//|     given by TrnSubset/TrnSubsetSize.                            |
//|   * training with ValSubsetSize>0, corresponds to early stopping |
//|     training with additional MaxIts/WStep stopping criteria.     |
//|     Training sample is given by TrnSubset/TrnSubsetSize,         |
//|     validation sample is given by ValSubset/ ValSubsetSize.      |
//+------------------------------------------------------------------+
void CMLPTrain::MLPTrainNetworkX(CMLPTrainer &s,
                                 int nrestarts,
                                 int algokind,
                                 CRowInt &trnsubset,
                                 int trnsubsetsize,
                                 CRowInt &valsubset,
                                 int valsubsetsize,
                                 CMultilayerPerceptron &network,
                                 CMLPReport &rep)
  {
//--- create variables
   CModelErrors modrep;
   double eval=0;
   double ebest=0;
   int    ngradbatch=0;
   int    nin=0;
   int    nout=0;
   int    wcount=0;
   int    pcount=0;
   int    itbest=0;
   int    itcnt=0;
   int    ntype=0;
   int    ttype=0;
   bool   rndstart;
   int    nr0=0;
   int    nr1=0;
   double bestrmserror=0;
   CSMLPTrnSession psession;
//--- function call
   CMLPBase::MLPProperties(network,nin,nout,wcount);
//--- Process root call
//--- Check correctness of parameters
   if(!CAp::Assert(algokind==0 || algokind==-1,__FUNCTION__+": unexpected AlgoKind"))
      return;
   if(!CAp::Assert(s.m_npoints>=0,__FUNCTION__+": internal error - parameter S is not initialized or is spoiled(S.NPoints<0)"))
      return;
   if(s.m_rcpar)
      ttype=0;
   else
      ttype=1;
   if(!CMLPBase::MLPIsSoftMax(network))
      ntype=0;
   else
      ntype=1;
//--- check
   if(!CAp::Assert(ntype==ttype,__FUNCTION__+": internal error - type of the training network is not similar to network type in trainer object"))
      return;
   if(!CAp::Assert(s.m_nin==nin,__FUNCTION__+": internal error - number of inputs in trainer is not equal to number of inputs in the training network."))
      return;
   if(!CAp::Assert(s.m_nout==nout,__FUNCTION__+": internal error - number of outputs in trainer is not equal to number of outputs in the training network."))
      return;
   if(!CAp::Assert(nrestarts>=0,__FUNCTION__+": internal error - NRestarts<0."))
      return;
   if(!CAp::Assert(trnsubset.Size()>=trnsubsetsize,__FUNCTION__+": internal error - parameter TrnSubsetSize more than input subset size(Length(TrnSubset)<TrnSubsetSize)"))
      return;
   if(!CAp::Assert(trnsubsetsize<=0 || (trnsubset.Min()>=0 && trnsubset.Max()<s.m_npoints),__FUNCTION__+": internal error - parameter TrnSubset contains incorrect index(TrnSubset[I]<0 or TrnSubset[I]>S.NPoints-1)"))
      return;
   if(!CAp::Assert(valsubset.Size()>=valsubsetsize,__FUNCTION__+": internal error - parameter ValSubsetSize more than input subset size(Length(ValSubset)<ValSubsetSize)"))
      return;
   if(!CAp::Assert(valsubsetsize<=0 || (valsubset.Min()>=0 && valsubset.Max()<s.m_npoints),__FUNCTION__+": internal error - parameter ValSubset contains incorrect index(ValSubset[I]<0 or ValSubset[I]>S.NPoints-1)"))
      return;
//--- Train
//--- * NRestarts>=1 means that network is restarted from random position
//--- * NRestarts=0 means that network is not randomized
   rndstart=nrestarts>0;
   bestrmserror=CMath::m_maxrealnumber;
   for(int iter=0; iter<=nrestarts; iter++)
     {
      rep.m_ngrad=0;
      rep.m_nhess=0;
      rep.m_ncholesky=0;
      InitMLPTrnSession(network,rndstart,s,psession);
      if((s.m_datatype==0 || s.m_datatype==1) && s.m_npoints>0 && trnsubsetsize!=0)
        {
         //--- Train network using combination of early stopping and step-size
         //--- and step-count based criteria. Network state with best value of
         //--- validation set error is stored in WBuf0. When validation set is
         //--- zero, most recent state of network is stored.
         ngradbatch=0;
         eval=0;
         ebest=0;
         itbest=0;
         itcnt=0;
         MLPStartTrainingX(s,rndstart,algokind,trnsubset,trnsubsetsize,psession);
         switch(s.m_datatype)
           {
            case 0:
               ebest=CMLPBase::MLPErrorSubset(psession.m_network,s.m_densexy,s.m_npoints,valsubset,valsubsetsize);
               break;
            case 1:
               ebest=CMLPBase::MLPErrorSparseSubset(psession.m_network,s.m_sparsexy,s.m_npoints,valsubset,valsubsetsize);
               break;
           }
         psession.m_wbuf0=psession.m_network.m_weights;
         while(MLPContinueTrainingX(s,trnsubset,trnsubsetsize,ngradbatch,psession))
           {
            switch(s.m_datatype)
              {
               case 0:
                  eval=CMLPBase::MLPErrorSubset(psession.m_network,s.m_densexy,s.m_npoints,valsubset,valsubsetsize);
                  break;
               case 1:
                  eval=CMLPBase::MLPErrorSparseSubset(psession.m_network,s.m_sparsexy,s.m_npoints,valsubset,valsubsetsize);
                  break;
              }
            if(eval<=ebest || valsubsetsize==0)
              {
               psession.m_wbuf0=psession.m_network.m_weights;
               ebest=eval;
               itbest=itcnt;
              }
            if(itcnt>30 && (double)(itcnt)>(double)(1.5*itbest))
               break;
            itcnt++;
           }
         psession.m_network.m_weights=psession.m_wbuf0;
         rep.m_ngrad=ngradbatch;
        }
      else
         psession.m_network.m_weights.Fill(0);
      //--- Evaluate network performance and update PSession.BestParameters/BestRMSError
      //--- (if needed).
      switch(s.m_datatype)
        {
         case 0:
            CMLPBase::MLPAllErrorsSubset(psession.m_network,s.m_densexy,s.m_npoints,trnsubset,trnsubsetsize,modrep);
            break;
         case 1:
            CMLPBase::MLPAllErrorsSparseSubset(psession.m_network,s.m_sparsexy,s.m_npoints,trnsubset,trnsubsetsize,modrep);
            break;
        }
      if(modrep.m_RMSError<psession.m_bestrmserror)
        {
         CMLPBase::MLPExportTunableParameters(psession.m_network,psession.m_bestparameters,pcount);
         psession.m_bestrmserror=modrep.m_RMSError;
        }
      //--- Choose best network
      if(psession.m_bestrmserror<bestrmserror)
        {
         CMLPBase::MLPImportTunableParameters(network,psession.m_bestparameters);
         bestrmserror=psession.m_bestrmserror;
        }
     }
//--- Calculate errors
   switch(s.m_datatype)
     {
      case 0:
         CMLPBase::MLPAllErrorsSubset(network,s.m_densexy,s.m_npoints,trnsubset,trnsubsetsize,modrep);
         break;
      case 1:
         CMLPBase::MLPAllErrorsSparseSubset(network,s.m_sparsexy,s.m_npoints,trnsubset,trnsubsetsize,modrep);
         break;
     }
   rep.m_RelCLSError=modrep.m_RelCLSError;
   rep.m_AvgCE=modrep.m_AvgCE;
   rep.m_RMSError=modrep.m_RMSError;
   rep.m_AvgError=modrep.m_AvgError;
   rep.m_AvgRelError=modrep.m_AvgRelError;
  }
//+------------------------------------------------------------------+
//| This function trains neural network ensemble passed to this      |
//| function using current dataset and early stopping training       |
//| algorithm. Each early stopping round performs NRestarts random   |
//| restarts (thus, EnsembleSize*NRestarts training rounds is        |
//| performed in total).                                             |
//+------------------------------------------------------------------+
void CMLPTrain::MLPTrainEnsembleX(CMLPTrainer &s,CMLPEnsemble &ensemble,
                                  int idx0,
                                  int idx1,
                                  int nrestarts,
                                  int trainingmethod,
                                  int ngrad)
  {
//--- create variables
   int nin=CMLPBase::MLPGetInputsCount(ensemble.m_network);
   int nout=CMLPBase::MLPGetOutputsCount(ensemble.m_network);
   int wcount=CMLPBase::MLPGetWeightsCount(ensemble.m_network);
   int pcount=0;
   int trnsubsetsize=0;
   int valsubsetsize=0;
   int k0=0;
   int i1_=0;
   CSMLPTrnSession psession;
   CHighQualityRandState rs;

   if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
      pcount=nin;
   else
      pcount=nin+nout;
   if(nrestarts<=0)
      nrestarts=1;
//--- Handle degenerate case
   if(s.m_npoints<2)
     {
      ensemble.m_weights.Fill(0.0);
      ensemble.m_columnmeans.Fill(0.0);
      ensemble.m_columnsigmas.Fill(1.0);
      return;
     }
//--- Prepare:
//--- * prepare MLPETrnSessions
//--- * fill ensemble by zeros (helps to detect errors)
   ensemble.m_weights.Fill(0.0);
   ensemble.m_columnmeans.Fill(0.0);
   ensemble.m_columnsigmas.Fill(0.0);
//--- Train
   CHighQualityRand::HQRndRandomize(rs);
   for(int k=idx0; k<idx1; k++)
     {
      InitMLPTrnSession(ensemble.m_network,true,s,psession);
      //--- Split set
      trnsubsetsize=0;
      valsubsetsize=0;
      if(trainingmethod==0)
        {
         psession.m_trnsubset.Resize(s.m_npoints);
         psession.m_valsubset.Resize(s.m_npoints);
         do
           {
            trnsubsetsize=0;
            valsubsetsize=0;
            for(int i=0; i<s.m_npoints; i++)
              {
               if(CMath::RandomReal()<0.66)
                 {
                  //--- Assign sample to training set
                  psession.m_trnsubset.Set(trnsubsetsize,i);
                  trnsubsetsize++;
                 }
               else
                 {
                  //--- Assign sample to validation set
                  psession.m_valsubset.Set(valsubsetsize,i);
                  valsubsetsize++;
                 }
              }
           }
         while(!(trnsubsetsize!=0 && valsubsetsize!=0));
         psession.m_trnsubset.Resize(trnsubsetsize);
         psession.m_valsubset.Resize(valsubsetsize);
        }
      if(trainingmethod==1)
        {
         valsubsetsize=0;
         trnsubsetsize=s.m_npoints;
         psession.m_trnsubset.Resize(trnsubsetsize);
         psession.m_valsubset.Resize(valsubsetsize);
         for(int i=0; i<s.m_npoints; i++)
            psession.m_trnsubset.Set(i,CHighQualityRand::HQRndUniformI(rs,s.m_npoints));
        }
      //--- Train
      MLPTrainNetworkX(s,nrestarts,-1,psession.m_trnsubset,trnsubsetsize,psession.m_valsubset,valsubsetsize,psession.m_network,psession.m_mlprep);
      ngrad+=psession.m_mlprep.m_ngrad;
      //--- Save results
      i1_=-(k*wcount);
      for(int i_=k*wcount; i_<(k+1)*wcount; i_++)
         ensemble.m_weights.Set(i_,psession.m_network.m_weights[i_+i1_]);
      i1_=-(k*pcount);
      for(int i_=k*pcount; i_<(k+1)*pcount ; i_++)
        {
         ensemble.m_columnmeans.Set(i_,psession.m_network.m_columnmeans[i_+i1_]);
         ensemble.m_columnsigmas.Set(i_,psession.m_network.m_columnsigmas[i_+i1_]);
        }
     }
  }
//+------------------------------------------------------------------+
//| This function performs step-by-step training of the neural       |
//| network. Here "step-by-step" means that training starts with     |
//| MLPStartTrainingX call, and then user subsequently calls         |
//| MLPContinueTrainingX to perform one more iteration of the        |
//| training.                                                        |
//| After call to this function trainer object remembers network and |
//| is ready to train it. However, no training is performed until    |
//| first call to MLPContinueTraining() function. Subsequent calls   |
//| to MLPContinueTraining() will advance traing progress one        |
//| iteration further.                                               |
//+------------------------------------------------------------------+
void CMLPTrain::MLPStartTrainingX(CMLPTrainer &s,bool randomstart,
                                  int algokind,CRowInt &subset,
                                  int subsetsize,
                                  CSMLPTrnSession &session)
  {
//--- create variables
   int nin=0;
   int nout=0;
   int wcount=0;
   int ntype=0;
   int ttype=0;
//--- Check parameters
   if(!CAp::Assert(s.m_npoints>=0,__FUNCTION__+": internal error - parameter S is not initialized or is spoiled(S.NPoints<0)"))
      return;
   if(!CAp::Assert(algokind==0 || algokind==-1,__FUNCTION__+": unexpected AlgoKind"))
      return;
   if(s.m_rcpar)
      ttype=0;
   else
      ttype=1;
   if(!CMLPBase::MLPIsSoftMax(session.m_network))
      ntype=0;
   else
      ntype=1;
   if(!CAp::Assert(ntype==ttype,__FUNCTION__+": internal error - type of the resulting network is not similar to network type in trainer object"))
      return;
   CMLPBase::MLPProperties(session.m_network,nin,nout,wcount);
   if(!CAp::Assert(s.m_nin==nin,__FUNCTION__+": number of inputs in trainer is not equal to number of inputs in the network."))
      return;
   if(!CAp::Assert(s.m_nout==nout,__FUNCTION__+": number of outputs in trainer is not equal to number of outputs in the network."))
      return;
   if(!CAp::Assert(subset.Size()>=subsetsize,__FUNCTION__+": internal error - parameter SubsetSize more than input subset size(Length(Subset)<SubsetSize)"))
      return;
   if(!CAp::Assert(subsetsize<=0 || (subset.Min()>=0 && subset.Max()<s.m_npoints),__FUNCTION__+": internal error - parameter Subset contains incorrect index(Subset[I]<0 or Subset[I]>S.NPoints-1)"))
      return;
//--- Prepare session
   CMinLBFGS::MinLBFGSSetCond(session.m_optimizer,0.0,0.0,s.m_wstep,s.m_maxits);
   if(s.m_npoints>0 && subsetsize!=0)
     {
      if(randomstart)
         CMLPBase::MLPRandomize(session.m_network);
      CMinLBFGS::MinLBFGSRestartFrom(session.m_optimizer,session.m_network.m_weights);
     }
   else
      session.m_network.m_weights.Fill(0);
//---
   if(algokind==-1)
     {
      session.m_algoused=s.m_algokind;
      if(s.m_algokind==1)
         session.m_minibatchsize=s.m_minibatchsize;
     }
   else
      session.m_algoused=0;
   CHighQualityRand::HQRndRandomize(session.m_generator);
   session.m_rstate.ia.Resize(16);
   session.m_rstate.ra.Resize(2);
   session.m_rstate.stage=-1;
  }
//+------------------------------------------------------------------+
//| This function performs step-by-step training of the neural       |
//| network. Here "step-by-step" means  that training starts with    |
//| MLPStartTrainingX call, and then user subsequently calls         |
//| MLPContinueTrainingX to perform one more iteration of the        |
//| training.                                                        |
//| This function performs one more iteration of the training and    |
//| returns either True (training continues) or False (training      |
//| stopped). In case True was returned, Network weights are updated |
//| according to the current state of the optimization progress. In  |
//| case False was returned, no additional updates is performed      |
//| (previous update of the network weights moved us to the final    |
//| point, and no additional updates is needed).                     |
//| EXAMPLE:                                                         |
//|      >                                                           |
//|      > [initialize network and trainer object]                   |
//|      >                                                           |
//|      > MLPStartTraining(Trainer, Network, True)                  |
//|      > while MLPContinueTraining(Trainer, Network) do            |
//|      >     [visualize training progress]                         |
//|      >                                                           |
//+------------------------------------------------------------------+
bool CMLPTrain::MLPContinueTrainingX(CMLPTrainer &s,CRowInt &subset,
                                     int subsetsize,
                                     int &ngradbatch,
                                     CSMLPTrnSession &session)
  {
//--- create variables
   int    nin=0;
   int    nout=0;
   int    wcount=0;
   int    twcount=0;
   int    ntype=0;
   int    ttype=0;
   double decay=0;
   double v=0;
   int    i=0;
   int    j=0;
   int    k=0;
   int    trnsetsize=0;
   int    epoch=0;
   int    minibatchcount=0;
   int    minibatchidx=0;
   int    cursize=0;
   int    idx0=0;
   int    idx1=0;
//--- Reverse communication preparations
//--- I know it looks ugly, but it works the same way
//--- anywhere from C++ to Python.
//--- This code initializes locals by:
//--- * random values determined during code
//---   generation - on first subroutine call
//--- * values from previous call - on subsequent calls
   if(session.m_rstate.stage>=0)
     {
      nin=session.m_rstate.ia[0];
      nout=session.m_rstate.ia[1];
      wcount=session.m_rstate.ia[2];
      twcount=session.m_rstate.ia[3];
      ntype=session.m_rstate.ia[4];
      ttype=session.m_rstate.ia[5];
      i=session.m_rstate.ia[6];
      j=session.m_rstate.ia[7];
      k=session.m_rstate.ia[8];
      trnsetsize=session.m_rstate.ia[9];
      epoch=session.m_rstate.ia[10];
      minibatchcount=session.m_rstate.ia[11];
      minibatchidx=session.m_rstate.ia[12];
      cursize=session.m_rstate.ia[13];
      idx0=session.m_rstate.ia[14];
      idx1=session.m_rstate.ia[15];
      decay=session.m_rstate.ra[0];
      v=session.m_rstate.ra[1];
     }
   else
     {
      nin=359;
      nout=-58;
      wcount=-919;
      twcount=-909;
      ntype=81;
      ttype=255;
      i=74;
      j=-788;
      k=809;
      trnsetsize=205;
      epoch=-838;
      minibatchcount=939;
      minibatchidx=-526;
      cursize=763;
      idx0=-541;
      idx1=-698;
      decay=-900;
      v=-318;
     }
   if(session.m_rstate.stage==0)
     {
      if(!MLPContinueTrainingX_lbl3(s,subset,subsetsize,ngradbatch,session,decay))
         return(false);
     }
   else
     {
      //--- Routine body
      //--- Check correctness of inputs
      if(!CAp::Assert(s.m_npoints>=0,__FUNCTION__+": internal error - parameter S is not initialized or is spoiled(S.NPoints<0)."))
         return(false);
      if(s.m_rcpar)
         ttype=0;
      else
         ttype=1;
      if(!CMLPBase::MLPIsSoftMax(session.m_network))
         ntype=0;
      else
         ntype=1;
      //--- check
      if(!CAp::Assert(ntype==ttype,__FUNCTION__+": internal error - type of the resulting network is not similar to network type in trainer object."))
         return(false);
      CMLPBase::MLPProperties(session.m_network,nin,nout,wcount);
      //--- check
      if(!CAp::Assert(s.m_nin==nin,__FUNCTION__+": internal error - number of inputs in trainer is not equal to number of inputs in the network."))
         return(false);
      if(!CAp::Assert(s.m_nout==nout,__FUNCTION__+": internal error - number of outputs in trainer is not equal to number of outputs in the network."))
         return(false);
      if(!CAp::Assert(subset.Size()>=subsetsize,__FUNCTION__+": internal error - parameter SubsetSize more than input subset size(Length(Subset)<SubsetSize)."))
         return(false);
      if(!CAp::Assert(subsetsize<=0 || (subset.Min()>=0 && subset.Max()<s.m_npoints),__FUNCTION__+": internal error - parameter Subset contains incorrect index(Subset[I]<0 or Subset[I]>S.NPoints-1)."))
         return(false);
      //--- Quick exit on empty training set
      if(s.m_npoints==0 || subsetsize==0)
         return(false);
      //--- Minibatch training
      if(!CAp::Assert(!(session.m_algoused==1),"MINIBATCH TRAINING IS NOT IMPLEMENTED YET"))
         return(false);
      //--- Last option: full batch training
      decay=s.m_decay;
      //---
      if(!MLPContinueTrainingX_lbl1(s,subset,subsetsize,ngradbatch,session,decay))
         return(false);
     }
//--- Saving state
   session.m_rstate.ia.Set(0,nin);
   session.m_rstate.ia.Set(1,nout);
   session.m_rstate.ia.Set(2,wcount);
   session.m_rstate.ia.Set(3,twcount);
   session.m_rstate.ia.Set(4,ntype);
   session.m_rstate.ia.Set(5,ttype);
   session.m_rstate.ia.Set(6,i);
   session.m_rstate.ia.Set(7,j);
   session.m_rstate.ia.Set(8,k);
   session.m_rstate.ia.Set(9,trnsetsize);
   session.m_rstate.ia.Set(10,epoch);
   session.m_rstate.ia.Set(11,minibatchcount);
   session.m_rstate.ia.Set(12,minibatchidx);
   session.m_rstate.ia.Set(13,cursize);
   session.m_rstate.ia.Set(14,idx0);
   session.m_rstate.ia.Set(15,idx1);
   session.m_rstate.ra.Set(0,decay);
   session.m_rstate.ra.Set(1,v);
//--- return result
   return(true);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CMLPTrain::MLPContinueTrainingX_lbl1(CMLPTrainer &s,CRowInt &subset,
                                          int subsetsize,
                                          int &ngradbatch,
                                          CSMLPTrnSession &session,double &decay)
  {
   if(!CMinLBFGS::MinLBFGSIteration(session.m_optimizer))
     {
      CMinLBFGS::MinLBFGSResultsBuf(session.m_optimizer,session.m_network.m_weights,session.m_optimizerrep);
      return(false);
     }
   if(!session.m_optimizer.m_xupdated)
     {
      if(!MLPContinueTrainingX_lbl3(s,subset,subsetsize,ngradbatch,session,decay))
         return(false);
     }
   session.m_network.m_weights=session.m_optimizer.m_x;
   session.m_rstate.stage=0;
//--- return result
   return(true);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CMLPTrain::MLPContinueTrainingX_lbl3(CMLPTrainer &s,CRowInt &subset,
                                          int subsetsize,
                                          int &ngradbatch,
                                          CSMLPTrnSession &session,double &decay)
  {
   session.m_network.m_weights=session.m_optimizer.m_x;
   switch(s.m_datatype)
     {
      case 0:
         CMLPBase::MLPGradBatchSubset(session.m_network,s.m_densexy,s.m_npoints,subset,subsetsize,session.m_optimizer.m_f,session.m_optimizer.m_g);
         break;
      case 1:
         CMLPBase::MLPGradBatchSparseSubset(session.m_network,s.m_sparsexy,s.m_npoints,subset,subsetsize,session.m_optimizer.m_f,session.m_optimizer.m_g);
         break;
     }
//--- Increment number of operations performed on batch gradient
   ngradbatch++;
   double v=session.m_network.m_weights.Dot(session.m_network.m_weights);
   session.m_optimizer.m_f+=0.5*decay*v;
   session.m_optimizer.m_g+=session.m_network.m_weights*decay+0;
//--- return result
   return(MLPContinueTrainingX_lbl1(s,subset,subsetsize,ngradbatch,session,decay));
  }
//+------------------------------------------------------------------+
//| Internal bagging subroutine.                                     |
//+------------------------------------------------------------------+
void CMLPTrain::MLPEBaggingInternal(CMLPEnsemble &ensemble,
                                    CMatrixDouble &xy,
                                    int npoints,
                                    double decay,
                                    int restarts,
                                    double wstep,
                                    int maxits,
                                    bool lmalgorithm,
                                    int &info,
                                    CMLPReport &rep,
                                    CMLPCVReport &ooberrors)
  {
//--- create variables
   CMatrixDouble xys;
   CMatrixDouble oobbuf;
   CRowInt oobcntbuf;
   CRowDouble x;
   CRowDouble y;
   CRowDouble dy;
   CRowDouble dsbuf;
   bool   s[];
   int    ccnt=0;
   int    pcnt=0;
   int    j=0;
   double v=0;
   int    nin=0;
   int    nout=0;
   int    wcount=0;
   int    i1_=0;
   CMLPReport tmprep;
   CHighQualityRandState rs;

   info=0;
   CMLPBase::MLPProperties(ensemble.m_network,nin,nout,wcount);
//--- Test for inputs
   if(!lmalgorithm && wstep==0.0 && maxits==0)
     {
      info=-8;
      return;
     }
   if(npoints<=0 || restarts<1 || wstep<0.0 || maxits<0)
     {
      info=-1;
      return;
     }
   if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
     {
      vector<double> temp=xy.Col(nin);
      if((int)MathRound(temp.Min())<0 || (int)MathRound(temp.Max())>=nout)
        {
         info=-2;
         return;
        }
     }
//--- allocate temporaries
   info=2;
   rep.m_ngrad=0;
   rep.m_nhess=0;
   rep.m_ncholesky=0;
   ooberrors.m_RelCLSError=0;
   ooberrors.m_AvgCE=0;
   ooberrors.m_RMSError=0;
   ooberrors.m_AvgError=0;
   ooberrors.m_AvgRelError=0;
   if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
     {
      ccnt=nin+1;
      pcnt=nin;
     }
   else
     {
      ccnt=nin+nout;
      pcnt=nin+nout;
     }
   xys.Resize(npoints,ccnt);
   ArrayResize(s,npoints);
   oobbuf=matrix<double>::Zeros(npoints,nout);
   oobcntbuf.Resize(npoints);
   oobcntbuf.Fill(0);
   x.Resize(nin);
   y.Resize(nout);
   if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
      dy.Resize(1);
   else
      dy.Resize(nout);
//--- main bagging cycle
   CHighQualityRand::HQRndRandomize(rs);
   for(int k=0; k<ensemble.m_ensemblesize; k++)
     {
      //--- prepare dataset
      ArrayInitialize(s,false);
      for(int i=0; i<npoints; i++)
        {
         j=CHighQualityRand::HQRndUniformI(rs,npoints);
         s[j]=true;
         xys.Row(i,xy[j]+0);
        }
      //--- train
      if(lmalgorithm)
         MLPTrainLM(ensemble.m_network,xys,npoints,decay,restarts,info,tmprep);
      else
         MLPTrainLBFGS(ensemble.m_network,xys,npoints,decay,restarts,wstep,maxits,info,tmprep);
      if(info<0)
         return;
      //--- save results
      rep.m_ngrad+=tmprep.m_ngrad;
      rep.m_nhess+=tmprep.m_nhess;
      rep.m_ncholesky+=tmprep.m_ncholesky;
      i1_= -(k*wcount);
      for(int i_=k*wcount; i_<(k+1)*wcount; i_++)
         ensemble.m_weights.Set(i_,ensemble.m_network.m_weights[i_+i1_]);
      i1_=-(k*pcnt);
      for(int i_=k*pcnt; i_<(k+1)*pcnt; i_++)
        {
         ensemble.m_columnmeans.Set(i_,ensemble.m_network.m_columnmeans[i_+i1_]);
         ensemble.m_columnsigmas.Set(i_,ensemble.m_network.m_columnsigmas[i_+i1_]);
        }
      //--- OOB estimates
      for(int i=0; i<npoints; i++)
        {
         if(!s[i])
           {
            x=xy[i]+0;
            CMLPBase::MLPProcess(ensemble.m_network,x,y);
            oobbuf.Row(i,y.ToVector()+oobbuf[i]);
            oobcntbuf.Add(i,1);
           }
        }
     }
//--- OOB estimates
   if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
      CBdSS::DSErrAllocate(nout,dsbuf);
   else
      CBdSS::DSErrAllocate(-nout,dsbuf);
   for(int i=0; i<npoints; i++)
     {
      if(oobcntbuf[i]!=0)
        {
         v=1.0/(double)oobcntbuf[i];
         y=oobbuf[i]*v;
         if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
            dy.Set(0,xy.Get(i,nin));
         else
           {
            i1_=(nin);
            for(int i_=0; i_<nout; i_++)
               dy.Set(i_,v*xy.Get(i,i_+i1_));
           }
         CBdSS::DSErrAccumulate(dsbuf,y,dy);
        }
     }
   CBdSS::DSErrFinish(dsbuf);
   ooberrors.m_RelCLSError=dsbuf[0];
   ooberrors.m_AvgCE=dsbuf[1];
   ooberrors.m_RMSError=dsbuf[2];
   ooberrors.m_AvgError=dsbuf[3];
   ooberrors.m_AvgRelError=dsbuf[4];
  }
//+------------------------------------------------------------------+
//| This function initializes temporaries needed for training        |
//| session.                                                         |
//+------------------------------------------------------------------+
void CMLPTrain::InitMLPTrnSession(CMultilayerPerceptron &networktrained,
                                  bool randomizenetwork,
                                  CMLPTrainer &trainer,
                                  CSMLPTrnSession &session)
  {
//--- create variables
   int nin=0;
   int nout=0;
   int wcount=0;
   int pcount=0;
   CRowInt dummysubset;
//--- Prepare network:
//--- * copy input network to Session.Network
//--- * re-initialize preprocessor and weights if RandomizeNetwork=True
   CMLPBase::MLPCopy(networktrained,session.m_network);
   if(randomizenetwork)
     {
      //--- check
      if(!CAp::Assert(trainer.m_datatype==0 || trainer.m_datatype==1,__FUNCTION__+": unexpected Trainer.DataType"))
         return;
      switch(trainer.m_datatype)
        {
         case 0:
            CMLPBase::MLPInitPreprocessorSubset(session.m_network,trainer.m_densexy,trainer.m_npoints,dummysubset,-1);
            break;
         case 1:
            CMLPBase::MLPInitPreprocessorSparseSubset(session.m_network,trainer.m_sparsexy,trainer.m_npoints,dummysubset,-1);
            break;
        }
      CMLPBase::MLPRandomize(session.m_network);
      session.m_randomizenetwork=true;
     }
   else
      session.m_randomizenetwork=false;
//--- Determine network geometry and initialize optimizer
   CMLPBase::MLPProperties(session.m_network,nin,nout,wcount);
   CMinLBFGS::MinLBFGSCreate(wcount,MathMin(wcount,trainer.m_lbfgsfactor),session.m_network.m_weights,session.m_optimizer);
   CMinLBFGS::MinLBFGSSetXRep(session.m_optimizer,true);
//--- Create buffers
   session.m_wbuf0.Resize(wcount);
   session.m_wbuf1.Resize(wcount);
//--- Initialize session result
   CMLPBase::MLPExportTunableParameters(session.m_network,session.m_bestparameters,pcount);
   session.m_bestrmserror=CMath::m_maxrealnumber;
  }
//+------------------------------------------------------------------+
//| Neural networks ensemble                                         |
//+------------------------------------------------------------------+
class CMLPE
  {
public:
   //--- class constants
   static const int  m_mlpefirstversion;
   //--- public methods
   static void       MLPECreate0(const int nin,const int nout,const int ensemblesize,CMLPEnsemble &ensemble);
   static void       MLPECreate1(const int nin,const int nhid,const int nout,const int ensemblesize,CMLPEnsemble &ensemble);
   static void       MLPECreate2(const int nin,const int nhid1,const int nhid2,const int nout,const int ensemblesize,CMLPEnsemble &ensemble);
   static void       MLPECreateB0(const int nin,const int nout,const double b,const double d,const int ensemblesize,CMLPEnsemble &ensemble);
   static void       MLPECreateB1(const int nin,const int nhid,const int nout,const double b,const double d,const int ensemblesize,CMLPEnsemble &ensemble);
   static void       MLPECreateB2(const int nin,const int nhid1,const int nhid2,const int nout,const double b,const double d,const int ensemblesize,CMLPEnsemble &ensemble);
   static void       MLPECreateR0(const int nin,const int nout,const double a,const double b,const int ensemblesize,CMLPEnsemble &ensemble);
   static void       MLPECreateR1(const int nin,const int nhid,const int nout,const double a,const double b,const int ensemblesize,CMLPEnsemble &ensemble);
   static void       MLPECreateR2(const int nin,const int nhid1,const int nhid2,const int nout,const double a,const double b,const int ensemblesize,CMLPEnsemble &ensemble);
   static void       MLPECreateC0(const int nin,const int nout,const int ensemblesize,CMLPEnsemble &ensemble);
   static void       MLPECreateC1(const int nin,const int nhid,const int nout,const int ensemblesize,CMLPEnsemble &ensemble);
   static void       MLPECreateC2(const int nin,const int nhid1,const int nhid2,const int nout,const int ensemblesize,CMLPEnsemble &ensemble);
   static void       MLPECreateFromNetwork(CMultilayerPerceptron &network,const int ensemblesize,CMLPEnsemble &ensemble);
   static void       MLPECopy(CMLPEnsemble &ensemble1,CMLPEnsemble &ensemble2);
   static void       MLPEAlloc(CSerializer &s,CMLPEnsemble &ensemble);
   static void       MLPESerialize(CSerializer &s,CMLPEnsemble &ensemble);
   static void       MLPEUnserialize(CSerializer &s,CMLPEnsemble &ensemble);
   static void       MLPERandomize(CMLPEnsemble &ensemble);
   static void       MLPEProperties(CMLPEnsemble &ensemble,int &nin,int &nout);
   static bool       MLPEIsSoftMax(CMLPEnsemble &ensemble);
   static void       MLPEProcess(CMLPEnsemble &ensemble,double &x[],double &y[]);
   static void       MLPEProcess(CMLPEnsemble &ensemble,CRowDouble &x,CRowDouble &y);
   static void       MLPEProcessI(CMLPEnsemble &ensemble,double &x[],double &y[]);
   static void       MLPEProcessI(CMLPEnsemble &ensemble,CRowDouble &x,CRowDouble &y);
   static void       MLPEAllErrorsX(CMLPEnsemble &ensemble,CMatrixDouble &densexy,CSparseMatrix &sparsexy,int datasetsize,int datasettype,CRowInt &idx,int subset0,int subset1,int subsettype,CModelErrors &rep);
   static void       MLPEAllerrorsSparse(CMLPEnsemble &ensemble,CSparseMatrix &xy,int npoints,double &relcls,double &avgce,double &rms,double &avg,double &avgrel);
   static double     MLPERelClsError(CMLPEnsemble &ensemble,CMatrixDouble &xy,const int npoints);
   static double     MLPEAvgCE(CMLPEnsemble &ensemble,CMatrixDouble &xy,const int npoints);
   static double     MLPERMSError(CMLPEnsemble &ensemble,CMatrixDouble &xy,const int npoints);
   static double     MLPEAvgError(CMLPEnsemble &ensemble,CMatrixDouble &xy,const int npoints);
   static double     MLPEAvgRelError(CMLPEnsemble &ensemble,CMatrixDouble &xy,const int npoints);

private:
   static void       MLPEAllErrors(CMLPEnsemble &ensemble,CMatrixDouble &xy,const int npoints,double &relcls,double &avgce,double &rms,double &avg,double &avgrel);
   static void       MLPEBaggingInternal(CMLPEnsemble &ensemble,CMatrixDouble &xy,const int npoints,const double decay,const int restarts,const double wstep,const int maxits,const bool lmalgorithm,int &info,CMLPReport &rep,CMLPCVReport &ooberrors);
  };
//+------------------------------------------------------------------+
//| Initialize constants                                             |
//+------------------------------------------------------------------+
const int CMLPE::m_mlpefirstversion=1;
//+------------------------------------------------------------------+
//| Like MLPCreate0, but for ensembles.                              |
//+------------------------------------------------------------------+
void CMLPE::MLPECreate0(const int nin,const int nout,const int ensemblesize,
                        CMLPEnsemble &ensemble)
  {
//--- object of class
   CMultilayerPerceptron net;
//--- function call
   CMLPBase::MLPCreate0(nin,nout,net);
//--- function call
   MLPECreateFromNetwork(net,ensemblesize,ensemble);
  }
//+------------------------------------------------------------------+
//| Like MLPCreate1, but for ensembles.                              |
//+------------------------------------------------------------------+
void CMLPE::MLPECreate1(const int nin,const int nhid,const int nout,
                        const int ensemblesize,CMLPEnsemble &ensemble)
  {
//--- object of class
   CMultilayerPerceptron net;
//--- function call
   CMLPBase::MLPCreate1(nin,nhid,nout,net);
//--- function call
   MLPECreateFromNetwork(net,ensemblesize,ensemble);
  }
//+------------------------------------------------------------------+
//| Like MLPCreate2, but for ensembles.                              |
//+------------------------------------------------------------------+
void CMLPE::MLPECreate2(const int nin,const int nhid1,const int nhid2,
                        const int nout,const int ensemblesize,
                        CMLPEnsemble &ensemble)
  {
//--- object of class
   CMultilayerPerceptron net;
//--- function call
   CMLPBase::MLPCreate2(nin,nhid1,nhid2,nout,net);
//--- function call
   MLPECreateFromNetwork(net,ensemblesize,ensemble);
  }
//+------------------------------------------------------------------+
//| Like MLPCreateB0, but for ensembles.                             |
//+------------------------------------------------------------------+
void CMLPE::MLPECreateB0(const int nin,const int nout,const double b,
                         const double d,const int ensemblesize,
                         CMLPEnsemble &ensemble)
  {
//--- object of class
   CMultilayerPerceptron net;
//--- function call
   CMLPBase::MLPCreateB0(nin,nout,b,d,net);
//--- function call
   MLPECreateFromNetwork(net,ensemblesize,ensemble);
  }
//+------------------------------------------------------------------+
//| Like MLPCreateB1, but for ensembles.                             |
//+------------------------------------------------------------------+
void CMLPE::MLPECreateB1(const int nin,const int nhid,const int nout,
                         const double b,const double d,const int ensemblesize,
                         CMLPEnsemble &ensemble)
  {
//--- object of class
   CMultilayerPerceptron net;
//--- function call
   CMLPBase::MLPCreateB1(nin,nhid,nout,b,d,net);
//--- function call
   MLPECreateFromNetwork(net,ensemblesize,ensemble);
  }
//+------------------------------------------------------------------+
//| Like MLPCreateB2, but for ensembles.                             |
//+------------------------------------------------------------------+
void CMLPE::MLPECreateB2(const int nin,const int nhid1,const int nhid2,
                         const int nout,const double b,const double d,
                         const int ensemblesize,CMLPEnsemble &ensemble)
  {
//--- object of class
   CMultilayerPerceptron net;
//--- function call
   CMLPBase::MLPCreateB2(nin,nhid1,nhid2,nout,b,d,net);
//--- function call
   MLPECreateFromNetwork(net,ensemblesize,ensemble);
  }
//+------------------------------------------------------------------+
//| Like MLPCreateR0, but for ensembles.                             |
//+------------------------------------------------------------------+
void CMLPE::MLPECreateR0(const int nin,const int nout,const double a,
                         const double b,const int ensemblesize,
                         CMLPEnsemble &ensemble)
  {
//--- object of class
   CMultilayerPerceptron net;
//--- function call
   CMLPBase::MLPCreateR0(nin,nout,a,b,net);
//--- function call
   MLPECreateFromNetwork(net,ensemblesize,ensemble);
  }
//+------------------------------------------------------------------+
//| Like MLPCreateR1, but for ensembles.                             |
//+------------------------------------------------------------------+
void CMLPE::MLPECreateR1(const int nin,const int nhid,const int nout,
                         const double a,const double b,
                         const int ensemblesize,CMLPEnsemble &ensemble)
  {
//--- object of class
   CMultilayerPerceptron net;
//--- function call
   CMLPBase::MLPCreateR1(nin,nhid,nout,a,b,net);
//--- function call
   MLPECreateFromNetwork(net,ensemblesize,ensemble);
  }
//+------------------------------------------------------------------+
//| Like MLPCreateR2, but for ensembles.                             |
//+------------------------------------------------------------------+
void CMLPE::MLPECreateR2(const int nin,const int nhid1,const int nhid2,
                         const int nout,const double a,const double b,
                         const int ensemblesize,CMLPEnsemble &ensemble)
  {
//--- object of class
   CMultilayerPerceptron net;
//--- function call
   CMLPBase::MLPCreateR2(nin,nhid1,nhid2,nout,a,b,net);
//--- function call
   MLPECreateFromNetwork(net,ensemblesize,ensemble);
  }
//+------------------------------------------------------------------+
//| Like MLPCreateC0, but for ensembles.                             |
//+------------------------------------------------------------------+
void CMLPE::MLPECreateC0(const int nin,const int nout,const int ensemblesize,
                         CMLPEnsemble &ensemble)
  {
//--- object of class
   CMultilayerPerceptron net;
//--- function call
   CMLPBase::MLPCreateC0(nin,nout,net);
//--- function call
   MLPECreateFromNetwork(net,ensemblesize,ensemble);
  }
//+------------------------------------------------------------------+
//| Like MLPCreateC1, but for ensembles.                             |
//+------------------------------------------------------------------+
void CMLPE::MLPECreateC1(const int nin,const int nhid,const int nout,
                         const int ensemblesize,CMLPEnsemble &ensemble)
  {
//--- object of class
   CMultilayerPerceptron net;
//--- function call
   CMLPBase::MLPCreateC1(nin,nhid,nout,net);
//--- function call
   MLPECreateFromNetwork(net,ensemblesize,ensemble);
  }
//+------------------------------------------------------------------+
//| Like MLPCreateC2, but for ensembles.                             |
//+------------------------------------------------------------------+
void CMLPE::MLPECreateC2(const int nin,const int nhid1,const int nhid2,
                         const int nout,const int ensemblesize,
                         CMLPEnsemble &ensemble)
  {
//--- object of class
   CMultilayerPerceptron net;
//--- function call
   CMLPBase::MLPCreateC2(nin,nhid1,nhid2,nout,net);
//--- function call
   MLPECreateFromNetwork(net,ensemblesize,ensemble);
  }
//+------------------------------------------------------------------+
//| Creates ensemble from network. Only network geometry is copied.  |
//+------------------------------------------------------------------+
void CMLPE::MLPECreateFromNetwork(CMultilayerPerceptron &network,
                                  const int ensemblesize,
                                  CMLPEnsemble &ensemble)
  {
//--- create variables
   int ccount=0;
   int i1_=0;
//--- check
   if(!CAp::Assert(ensemblesize>0,__FUNCTION__+": incorrect ensemble size!"))
      return;
//--- Copy network
   CMLPBase::MLPCopy(network,ensemble.m_network);
//--- network properties
   if(CMLPBase::MLPIsSoftMax(network))
      ccount=CMLPBase::MLPGetInputsCount(ensemble.m_network);
   else
      ccount=CMLPBase::MLPGetInputsCount(ensemble.m_network)+CMLPBase::MLPGetOutputsCount(ensemble.m_network);
   int wcount=CMLPBase::MLPGetWeightsCount(ensemble.m_network);
   ensemble.m_ensemblesize=ensemblesize;
//--- weights, means, sigmas
   ensemble.m_weights=vector<double>::Zeros(ensemblesize*wcount);
   ensemble.m_columnmeans=vector<double>::Zeros(ensemblesize*ccount);
   ensemble.m_columnsigmas=vector<double>::Zeros(ensemblesize*ccount);
   for(int i=0; i<ensemblesize*wcount; i++)
      ensemble.m_weights.Set(i,CMath::RandomReal()-0.5);
   for(int i=0; i<ensemblesize; i++)
     {
      i1_=-(i*ccount);
      for(int i_=i*ccount; i_<(i+1)*ccount; i_++)
        {
         ensemble.m_columnmeans.Set(i_,network.m_columnmeans[i_+i1_]);
         ensemble.m_columnsigmas.Set(i_,network.m_columnsigmas[i_+i1_]);
        }
     }
//--- temporaries, internal buffers
   ensemble.m_y.Resize(CMLPBase::MLPGetOutputsCount(ensemble.m_network));
  }
//+------------------------------------------------------------------+
//| Copying of MLPEnsemble strucure                                  |
//| INPUT PARAMETERS:                                                |
//|     Ensemble1 -   original                                       |
//| OUTPUT PARAMETERS:                                               |
//|     Ensemble2 -   copy                                           |
//+------------------------------------------------------------------+
void CMLPE::MLPECopy(CMLPEnsemble &ensemble1,CMLPEnsemble &ensemble2)
  {
   ensemble2=ensemble1;
  }
//+------------------------------------------------------------------+
//| Serializer: allocation                                           |
//+------------------------------------------------------------------+
void CMLPE::MLPEAlloc(CSerializer &s,CMLPEnsemble &ensemble)
  {
   s.Alloc_Entry();
   s.Alloc_Entry();
   s.Alloc_Entry();
   CApServ::AllocRealArray(s,ensemble.m_weights,-1);
   CApServ::AllocRealArray(s,ensemble.m_columnmeans,-1);
   CApServ::AllocRealArray(s,ensemble.m_columnsigmas,-1);
   CMLPBase::MLPAlloc(s,ensemble.m_network);
  }
//+------------------------------------------------------------------+
//| Serializer: serialization                                        |
//+------------------------------------------------------------------+
void CMLPE::MLPESerialize(CSerializer &s,CMLPEnsemble &ensemble)
  {
   s.Serialize_Int(CSCodes::GetMLPESerializationCode());
   s.Serialize_Int(m_mlpefirstversion);
   s.Serialize_Int(ensemble.m_ensemblesize);
   CApServ::SerializeRealArray(s,ensemble.m_weights,-1);
   CApServ::SerializeRealArray(s,ensemble.m_columnmeans,-1);
   CApServ::SerializeRealArray(s,ensemble.m_columnsigmas,-1);
   CMLPBase::MLPSerialize(s,ensemble.m_network);
  }
//+------------------------------------------------------------------+
//| Serializer: unserialization                                      |
//+------------------------------------------------------------------+
void CMLPE::MLPEUnserialize(CSerializer &s,CMLPEnsemble &ensemble)
  {
//--- check correctness of header
   int i0=s.Unserialize_Int();
   if(!CAp::Assert(i0==CSCodes::GetMLPESerializationCode(),__FUNCTION__": stream header corrupted"))
      return;
   int i1=s.Unserialize_Int();
   if(!CAp::Assert(i1==m_mlpefirstversion,__FUNCTION__": stream header corrupted"))
      return;
//--- Create network
   ensemble.m_ensemblesize=s.Unserialize_Int();
   CApServ::UnserializeRealArray(s,ensemble.m_weights);
   CApServ::UnserializeRealArray(s,ensemble.m_columnmeans);
   CApServ::UnserializeRealArray(s,ensemble.m_columnsigmas);
   CMLPBase::MLPUnserialize(s,ensemble.m_network);
//--- Allocate termoraries
   ensemble.m_y.Resize(CMLPBase::MLPGetOutputsCount(ensemble.m_network));
  }
//+------------------------------------------------------------------+
//| Randomization of MLP ensemble                                    |
//+------------------------------------------------------------------+
void CMLPE::MLPERandomize(CMLPEnsemble &ensemble)
  {
//--- calculation
   int wcount=CMLPBase::MLPGetWeightsCount(ensemble.m_network);
   for(int i=0; i<=ensemble.m_ensemblesize*wcount-1; i++)
      ensemble.m_weights.Set(i,CMath::RandomReal()-0.5);
  }
//+------------------------------------------------------------------+
//| Return ensemble properties (number of inputs and outputs).       |
//+------------------------------------------------------------------+
void CMLPE::MLPEProperties(CMLPEnsemble &ensemble,int &nin,int &nout)
  {
//--- change values
   nin=CMLPBase::MLPGetInputsCount(ensemble.m_network);
   nout=CMLPBase::MLPGetOutputsCount(ensemble.m_network);
  }
//+------------------------------------------------------------------+
//| Return normalization type (whether ensemble is SOFTMAX-normalized|
//| or not).                                                         |
//+------------------------------------------------------------------+
bool CMLPE::MLPEIsSoftMax(CMLPEnsemble &ensemble)
  {
   return(CMLPBase::MLPIsSoftMax(ensemble.m_network));
  }
//+------------------------------------------------------------------+
//| Procesing                                                        |
//| INPUT PARAMETERS:                                                |
//|     Ensemble-   neural networks ensemble                         |
//|     X       -   input vector,  array[0..NIn-1].                  |
//|     Y       -   (possibly) preallocated buffer; if size of Y is  |
//|                 less than NOut, it will be reallocated. If it is |
//|                 large enough, it is NOT reallocated, so we can   |
//|                 save some time on reallocation.                  |
//| OUTPUT PARAMETERS:                                               |
//|     Y       -   result. Regression estimate when solving         |
//|                 regression task, vector of posterior             |
//|                 probabilities for classification task.           |
//+------------------------------------------------------------------+
void CMLPE::MLPEProcess(CMLPEnsemble &ensemble,double &x[],double &y[])
  {
   CRowDouble X=x;
   CRowDouble Y;
   MLPEProcess(ensemble,X,Y);
   Y.ToArray(y);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CMLPE::MLPEProcess(CMLPEnsemble &ensemble,CRowDouble &x,CRowDouble &y)
  {
//--- create variables
   int    es=0;
   int    wc=0;
   int    cc=0;
   double v=0;
   int    nout=0;
   int    i1_=0;
//--- initialization
   es=ensemble.m_ensemblesize;
   wc=CMLPBase::MLPGetWeightsCount(ensemble.m_network);
   if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
      cc=CMLPBase::MLPGetInputsCount(ensemble.m_network);
   else
      cc=CMLPBase::MLPGetInputsCount(ensemble.m_network)+CMLPBase::MLPGetOutputsCount(ensemble.m_network);
//--- calculate
   v=1.0/(double)es;
   nout=CMLPBase::MLPGetOutputsCount(ensemble.m_network);
   y=vector<double>::Zeros(nout);
   for(int i=0; i<es; i++)
     {
      i1_=(i*wc);
      for(int i_=0; i_<wc; i_++)
         ensemble.m_network.m_weights.Set(i_,ensemble.m_weights[i_+i1_]);
      i1_=(i*cc);
      for(int i_=0; i_<cc; i_++)
         ensemble.m_network.m_columnmeans.Set(i_,ensemble.m_columnmeans[i_+i1_]);
      i1_=(i*cc);
      for(int i_=0; i_<cc; i_++)
         ensemble.m_network.m_columnsigmas.Set(i_,ensemble.m_columnsigmas[i_+i1_]);
      CMLPBase::MLPProcess(ensemble.m_network,x,ensemble.m_y);
      for(int i_=0; i_<nout; i_++)
         y.Add(i_,v*ensemble.m_y[i_]);
     }
  }
//+------------------------------------------------------------------+
//| 'interactive' variant of MLPEProcess for languages like Python   |
//| which support constructs like "Y = MLPEProcess(LM,X)" and        |
//| interactive mode of the interpreter                              |
//| This function allocates new array on each call, so it is         |
//| significantly slower than its 'non-interactive' counterpart, but |
//| it is more convenient when you call it from command line.        |
//+------------------------------------------------------------------+
void CMLPE::MLPEProcessI(CMLPEnsemble &ensemble,double &x[],double &y[])
  {
   MLPEProcess(ensemble,x,y);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CMLPE::MLPEProcessI(CMLPEnsemble &ensemble,CRowDouble &x,CRowDouble &y)
  {
   MLPEProcess(ensemble,x,y);
  }
//+------------------------------------------------------------------+
//| Calculation of all types of errors                               |
//+------------------------------------------------------------------+
void CMLPE::MLPEAllErrorsX(CMLPEnsemble &ensemble,
                           CMatrixDouble &densexy,
                           CSparseMatrix &sparsexy,
                           int datasetsize,
                           int datasettype,
                           CRowInt &idx,
                           int subset0,
                           int subset1,
                           int subsettype,
                           CModelErrors &rep)
  {
//--- create variables
   int  nin=0;
   int  nout=0;
   bool iscls;
   int  srcidx=0;
   int  i1_=0;
   CMLPBuffers pbuf;
   CModelErrors rep0;
   CModelErrors rep1;
//--- Get network information
   nin=CMLPBase::MLPGetInputsCount(ensemble.m_network);
   nout=CMLPBase::MLPGetOutputsCount(ensemble.m_network);
   iscls=CMLPBase::MLPIsSoftMax(ensemble.m_network);
//--- Retrieve buffer, prepare, process data, recycle buffer
   if(iscls)
      CBdSS::DSErrAllocate(nout,pbuf.m_Tmp0);
   else
      CBdSS::DSErrAllocate(-nout,pbuf.m_Tmp0);
   CApServ::RVectorSetLengthAtLeast(pbuf.m_X,nin);
   CApServ::RVectorSetLengthAtLeast(pbuf.m_Y,nout);
   CApServ::RVectorSetLengthAtLeast(pbuf.m_Desiredy,nout);
   for(int i=subset0; i<subset1; i++)
     {
      srcidx=-1;
      if(subsettype==0)
         srcidx=i;
      if(subsettype==1)
         srcidx=idx[i];
      if(!CAp::Assert(srcidx>=0,__FUNCTION__": internal error"))
         return;
      switch(datasettype)
        {
         case 0:
            pbuf.m_X=densexy[srcidx]+0;
            break;
         case 1:
            CSparse::SparseGetRow(sparsexy,srcidx,pbuf.m_X);
            break;
         default:
            CAp::Assert(false,__FUNCTION__": wrong Datase Type");
            return;
        }
      pbuf.m_X.Resize(nin);
      MLPEProcess(ensemble,pbuf.m_X,pbuf.m_Y);
      if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
        {
         if(datasettype==0)
            pbuf.m_Desiredy.Set(0,densexy.Get(srcidx,nin));
         else
            pbuf.m_Desiredy.Set(0,CSparse::SparseGet(sparsexy,srcidx,nin));
        }
      else
        {
         if(datasettype==0)
           {
            i1_=nin;
            for(int i_=0; i_<nout; i_++)
               pbuf.m_Desiredy.Set(i_,densexy.Get(srcidx,i_+i1_));
           }
         else
           {
            for(int j=0; j<nout; j++)
               pbuf.m_Desiredy.Set(j,CSparse::SparseGet(sparsexy,srcidx,nin+j));
           }
        }
      CBdSS::DSErrAccumulate(pbuf.m_Tmp0,pbuf.m_Y,pbuf.m_Desiredy);
     }
   CBdSS::DSErrFinish(pbuf.m_Tmp0);
   rep.m_RelCLSError=pbuf.m_Tmp0[0];
   rep.m_AvgCE=pbuf.m_Tmp0[1]/MathLog(2);
   rep.m_RMSError=pbuf.m_Tmp0[2];
   rep.m_AvgError=pbuf.m_Tmp0[3];
   rep.m_AvgRelError=pbuf.m_Tmp0[4];
  }
//+------------------------------------------------------------------+
//| Calculation of all types of errors on dataset given by sparse    |
//| matrix                                                           |
//+------------------------------------------------------------------+
void CMLPE::MLPEAllerrorsSparse(CMLPEnsemble &ensemble,
                                CSparseMatrix &xy,
                                int npoints,double &relcls,
                                double &avgce,double &rms,
                                double &avg,double &avgrel)
  {
//--- create variables
   int nin=CMLPBase::MLPGetInputsCount(ensemble.m_network);
   int nout=CMLPBase::MLPGetOutputsCount(ensemble.m_network);
   int i1_=0;
   CRowDouble buf;
   CRowDouble workx;
   CRowDouble y;
   CRowDouble dy;

   relcls=0;
   avgce=0;
   rms=0;
   avg=0;
   avgrel=0;

   if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
     {
      dy.Resize(1);
      CBdSS::DSErrAllocate(nout,buf);
     }
   else
     {
      dy.Resize(nout);
      CBdSS::DSErrAllocate(-nout,buf);
     }
   for(int i=0; i<npoints; i++)
     {
      CSparse::SparseGetRow(xy,i,workx);
      MLPEProcess(ensemble,workx,y);
      if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
         dy.Set(0,workx[nin]);
      else
        {
         i1_=nin;
         for(int i_=0; i_<=nout-1; i_++)
            dy.Set(i_,workx[i_+i1_]);
        }
      CBdSS::DSErrAccumulate(buf,y,dy);
     }
   CBdSS::DSErrFinish(buf);
   relcls=buf[0];
   avgce=buf[1];
   rms=buf[2];
   avg=buf[3];
   avgrel=buf[4];
  }
//+------------------------------------------------------------------+
//| Relative classification error on the test set                    |
//| INPUT PARAMETERS:                                                |
//|     Ensemble-   ensemble                                         |
//|     XY      -   test set                                         |
//|     NPoints -   test set size                                    |
//| RESULT:                                                          |
//|     percent of incorrectly classified cases.                     |
//|     Works both for classifier betwork and for regression networks|
//|     which are used as classifiers.                               |
//+------------------------------------------------------------------+
double CMLPE::MLPERelClsError(CMLPEnsemble &ensemble,CMatrixDouble &xy,
                              const int npoints)
  {
//--- create variables
   CModelErrors rep;
//--- function call
   MLPEAllErrorsX(ensemble,xy,ensemble.m_network.m_dummysxy,npoints,0,ensemble.m_network.m_dummyidx,0,npoints,0,rep);
//--- return result
   return(rep.m_RelCLSError);
  }
//+------------------------------------------------------------------+
//| Average cross-entropy (in bits per element) on the test set      |
//| INPUT PARAMETERS:                                                |
//|     Ensemble-   ensemble                                         |
//|     XY      -   test set                                         |
//|     NPoints -   test set size                                    |
//| RESULT:                                                          |
//|     CrossEntropy/(NPoints*LN(2)).                                |
//|     Zero if ensemble solves regression task.                     |
//+------------------------------------------------------------------+
double CMLPE::MLPEAvgCE(CMLPEnsemble &ensemble,CMatrixDouble &xy,
                        const int npoints)
  {
//--- create variables
   CModelErrors rep;
//--- function call
   MLPEAllErrorsX(ensemble,xy,ensemble.m_network.m_dummysxy,npoints,0,ensemble.m_network.m_dummyidx,0,npoints,0,rep);
//--- return result
   return(rep.m_AvgCE);
  }
//+------------------------------------------------------------------+
//| RMS error on the test set                                        |
//| INPUT PARAMETERS:                                                |
//|     Ensemble-   ensemble                                         |
//|     XY      -   test set                                         |
//|     NPoints -   test set size                                    |
//| RESULT:                                                          |
//|     root mean square error.                                      |
//|     Its meaning for regression task is obvious. As for           |
//|     classification task RMS error means error when estimating    |
//|     posterior probabilities.                                     |
//+------------------------------------------------------------------+
double CMLPE::MLPERMSError(CMLPEnsemble &ensemble,CMatrixDouble &xy,
                           const int npoints)
  {
//--- create variables
   CModelErrors rep;
//--- function call
   MLPEAllErrorsX(ensemble,xy,ensemble.m_network.m_dummysxy,npoints,0,ensemble.m_network.m_dummyidx,0,npoints,0,rep);
//--- return result
   return(rep.m_RMSError);
  }
//+------------------------------------------------------------------+
//| Average error on the test set                                    |
//| INPUT PARAMETERS:                                                |
//|     Ensemble-   ensemble                                         |
//|     XY      -   test set                                         |
//|     NPoints -   test set size                                    |
//| RESULT:                                                          |
//|     Its meaning for regression task is obvious. As for           |
//|     classification task it means average error when estimating   |
//|     posterior probabilities.                                     |
//+------------------------------------------------------------------+
double CMLPE::MLPEAvgError(CMLPEnsemble &ensemble,CMatrixDouble &xy,
                           const int npoints)
  {
//--- create variables
   CModelErrors rep;
//--- function call
   MLPEAllErrorsX(ensemble,xy,ensemble.m_network.m_dummysxy,npoints,0,ensemble.m_network.m_dummyidx,0,npoints,0,rep);
//--- return result
   return(rep.m_AvgError);
  }
//+------------------------------------------------------------------+
//| Average relative error on the test set                           |
//| INPUT PARAMETERS:                                                |
//|     Ensemble-   ensemble                                         |
//|     XY      -   test set                                         |
//|     NPoints -   test set size                                    |
//| RESULT:                                                          |
//|     Its meaning for regression task is obvious. As for           |
//|     classification task it means average relative error when     |
//|     estimating posterior probabilities.                          |
//+------------------------------------------------------------------+
double CMLPE::MLPEAvgRelError(CMLPEnsemble &ensemble,CMatrixDouble &xy,
                              const int npoints)
  {
//--- create variables
   CModelErrors rep;
//--- function call
   MLPEAllErrorsX(ensemble,xy,ensemble.m_network.m_dummysxy,npoints,0,ensemble.m_network.m_dummyidx,0,npoints,0,rep);
//--- return result
   return(rep.m_AvgRelError);
  }
//+------------------------------------------------------------------+
//| Training neural networks ensemble using  bootstrap  aggregating  |
//| (bagging).                                                       |
//| Modified Levenberg-Marquardt algorithm is used as base training  |
//| method.                                                          |
//| INPUT PARAMETERS:                                                |
//|     Ensemble    -   model with initialized geometry              |
//|     XY          -   training set                                 |
//|     NPoints     -   training set size                            |
//|     Decay       -   weight decay coefficient, >=0.001            |
//|     Restarts    -   restarts, >0.                                |
//| OUTPUT PARAMETERS:                                               |
//|     Ensemble    -   trained model                                |
//|     Info        -   return code:                                 |
//|                     * -2, if there is a point with class number  |
//|                           outside of [0..NClasses-1].            |
//|                     * -1, if incorrect parameters was passed     |
//|                           (NPoints<0, Restarts<1).               |
//|                     *  2, if task has been solved.               |
//|     Rep         -   training report.                             |
//|     OOBErrors   -   out-of-bag generalization error estimate     |
//+------------------------------------------------------------------+
void CMLPTrain::MLPEBaggingLM(CMLPEnsemble &ensemble,CMatrixDouble &xy,
                              const int npoints,const double decay,
                              const int restarts,int &info,CMLPReport &rep,
                              CMLPCVReport &ooberrors)
  {
//--- initialization
   info=0;
//--- function call
   MLPEBaggingInternal(ensemble,xy,npoints,decay,restarts,0.0,0,true,info,rep,ooberrors);
  }
//+------------------------------------------------------------------+
//| Training neural networks ensemble using  bootstrap aggregating   |
//| (bagging). L-BFGS algorithm is used as base training method.     |
//| INPUT PARAMETERS:                                                |
//|     Ensemble    -   model with initialized geometry              |
//|     XY          -   training set                                 |
//|     NPoints     -   training set size                            |
//|     Decay       -   weight decay coefficient, >=0.001            |
//|     Restarts    -   restarts, >0.                                |
//|     WStep       -   stopping criterion, same as in MLPTrainLBFGS |
//|     MaxIts      -   stopping criterion, same as in MLPTrainLBFGS |
//| OUTPUT PARAMETERS:                                               |
//|     Ensemble    -   trained model                                |
//|     Info        -   return code:                                 |
//|                     * -8, if both WStep=0 and MaxIts=0           |
//|                     * -2, if there is a point with class number  |
//|                           outside of [0..NClasses-1].            |
//|                     * -1, if incorrect parameters was passed     |
//|                           (NPoints<0, Restarts<1).               |
//|                     *  2, if task has been solved.               |
//|     Rep         -   training report.                             |
//|     OOBErrors   -   out-of-bag generalization error estimate     |
//+------------------------------------------------------------------+
void CMLPTrain::MLPEBaggingLBFGS(CMLPEnsemble &ensemble,CMatrixDouble &xy,
                                 const int npoints,const double decay,
                                 const int restarts,const double wstep,
                                 const int maxits,int &info,CMLPReport &rep,
                                 CMLPCVReport &ooberrors)
  {
//--- initialization
   info=0;
//--- function call
   MLPEBaggingInternal(ensemble,xy,npoints,decay,restarts,wstep,maxits,false,info,rep,ooberrors);
  }
//+------------------------------------------------------------------+
//| Training neural networks ensemble using early stopping.          |
//| INPUT PARAMETERS:                                                |
//|     Ensemble    -   model with initialized geometry              |
//|     XY          -   training set                                 |
//|     NPoints     -   training set size                            |
//|     Decay       -   weight decay coefficient, >=0.001            |
//|     Restarts    -   restarts, >0.                                |
//| OUTPUT PARAMETERS:                                               |
//|     Ensemble    -   trained model                                |
//|     Info        -   return code:                                 |
//|                     * -2, if there is a point with class number  |
//|                           outside of [0..NClasses-1].            |
//|                     * -1, if incorrect parameters was passed     |
//|                           (NPoints<0, Restarts<1).               |
//|                     *  6, if task has been solved.               |
//|     Rep         -   training report.                             |
//|     OOBErrors   -   out-of-bag generalization error estimate     |
//+------------------------------------------------------------------+
void CMLPTrain::MLPETrainES(CMLPEnsemble &ensemble,CMatrixDouble &xy,
                            const int npoints,const double decay,
                            const int restarts,int &info,CMLPReport &rep)
  {
//--- create variables
   int ccount=0;
   int pcount=0;
   int trnsize=0;
   int valsize=0;
   int tmpinfo=0;
   int nin=0;
   int nout=0;
   int wcount=0;
   int i1_=0;
//--- create matrix
   CMatrixDouble trnxy;
   CMatrixDouble valxy;
//--- objects of classes
   CMLPReport            tmprep;
   CModelErrors          moderr;
//--- initialization
   info=0;
   nin=CMLPBase::MLPGetInputsCount(ensemble.m_network);
   nout=CMLPBase::MLPGetOutputsCount(ensemble.m_network);
   wcount=CMLPBase::MLPGetWeightsCount(ensemble.m_network);
//--- check
   if((npoints<2 || restarts<1) || decay<0.0)
     {
      info=-1;
      return;
     }
//--- check
   if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
     {
      vector<double> temp=xy.Col(nin);
      temp.Resize(npoints);
      //--- check
      if((int)MathRound(temp.Min())<0 || (int)MathRound(temp.Max())>=nout)
        {
         info=-2;
         return;
        }
      ccount=nin+1;
      pcount=nin;
     }
   else
     {
      ccount=nin+nout;
      pcount=nin+nout;
     }
//--- change value
   info=6;
//--- allocation
   trnxy.Resize(npoints,ccount);
   valxy.Resize(npoints,ccount);
//--- change values
   rep.m_ngrad=0;
   rep.m_nhess=0;
   rep.m_ncholesky=0;
//--- train networks
   for(int k=0; k<=ensemble.m_ensemblesize-1; k++)
     {
      //--- Split set
      do
        {
         trnsize=0;
         valsize=0;
         for(int i=0; i<npoints; i++)
           {
            //--- check
            if(CMath::RandomReal()<0.66)
              {
               //--- Assign sample to training set
               trnxy.Row(trnsize,xy[i]+0);
               trnsize++;
              }
            else
              {
               //--- Assign sample to validation set
               valxy.Row(valsize,xy[i]+0);
               valsize++;
              }
           }
        }
      while(!(trnsize!=0 && valsize!=0));
      //--- Train
      MLPTrainES(ensemble.m_network,trnxy,trnsize,valxy,valsize,decay,restarts,tmpinfo,tmprep);
      //--- check
      if(tmpinfo<0)
        {
         info=tmpinfo;
         return;
        }
      //--- save results
      i1_=-(k*wcount);
      for(int i_=k*wcount; i_<(k+1)*wcount; i_++)
         ensemble.m_weights.Set(i_,ensemble.m_network.m_weights[i_+i1_]);
      i1_=-(k*pcount);
      for(int i_=k*pcount; i_<(k+1)*pcount; i_++)
         ensemble.m_columnmeans.Set(i_,ensemble.m_network.m_columnmeans[i_+i1_]);
      i1_=-(k*pcount);
      for(int i_=k*pcount; i_<(k+1)*pcount; i_++)
         ensemble.m_columnsigmas.Set(i_,ensemble.m_network.m_columnsigmas[i_+i1_]);
      //--- change values
      rep.m_ngrad+=tmprep.m_ngrad;
      rep.m_nhess+=tmprep.m_nhess;
      rep.m_ncholesky+=tmprep.m_ncholesky;
     }
   CMLPE::MLPEAllErrorsX(ensemble,xy,ensemble.m_network.m_dummysxy,npoints,0,ensemble.m_network.m_dummyidx,0,npoints,0,moderr);
   rep.m_RelCLSError=moderr.m_RelCLSError;
   rep.m_AvgCE=moderr.m_AvgCE;
   rep.m_RMSError=moderr.m_RMSError;
   rep.m_AvgError=moderr.m_AvgError;
   rep.m_AvgRelError=moderr.m_AvgRelError;
  }
//+------------------------------------------------------------------+
//| This function trains neural network ensemble passed to this      |
//| function using current dataset and early stopping training       |
//| algorithm. Each early stopping round performs NRestarts random   |
//| restarts (thus, EnsembleSize*NRestarts training rounds is        |
//| performed in total).                                             |
//| INPUT PARAMETERS:                                                |
//|   S           -  trainer object;                                 |
//|   Ensemble    -  neural network ensemble. It must have same      |
//|                  number of inputs and outputs/classes as was     |
//|                  specified during creation of the trainer object.|
//|   NRestarts   -  number of restarts, >=0:                        |
//|                  * NRestarts>0 means that specified number of    |
//|                    random restarts are performed during each ES  |
//|                    round;                                        |
//|                  * NRestarts=0 is silently replaced by 1.        |
//| OUTPUT PARAMETERS:                                               |
//|   Ensemble    -  trained ensemble;                               |
//|   Rep         -  it contains all type of errors.                 |
//| NOTE: this training method uses BOTH early stopping and weight   |
//|       decay! So, you should select weight decay before starting  |
//|       training just as you select it before training             |
//|       "conventional" networks.                                   |
//| NOTE: when no dataset was specified with MLPSetDataset /         |
//|       SetSparseDataset(), or single-point dataset was passed,    |
//|       ensemble is filled by zero values.                         |
//| NOTE: this method uses sum-of-squares error function for training|
//+------------------------------------------------------------------+
void CMLPTrain::MLPTrainEnsembleES(CMLPTrainer &s,
                                   CMLPEnsemble &ensemble,
                                   int nrestarts,
                                   CMLPReport &rep)
  {
//--- create variables
   int nin=0;
   int nout=0;
   int ntype=0;
   int ttype=0;
   int sgrad=0;
   CModelErrors tmprep;
//--- check
   if(!CAp::Assert(s.m_npoints>=0,__FUNCTION__+": parameter S is not initialized or is spoiled(S.NPoints<0)"))
      return;
   if(!CMLPE::MLPEIsSoftMax(ensemble))
      ntype=0;
   else
      ntype=1;
   if(s.m_rcpar)
      ttype=0;
   else
      ttype=1;
//--- check
   if(!CAp::Assert(ntype==ttype,__FUNCTION__+": internal error - type of input network is not similar to network type in trainer object"))
      return;
   nin=CMLPBase::MLPGetInputsCount(ensemble.m_network);
//--- check
   if(!CAp::Assert(s.m_nin==nin,__FUNCTION__+": number of inputs in trainer is not equal to number of inputs in ensemble network"))
      return;

   nout=CMLPBase::MLPGetOutputsCount(ensemble.m_network);
//--- check
   if(!CAp::Assert(s.m_nout==nout,__FUNCTION__+": number of outputs in trainer is not equal to number of outputs in ensemble network"))
      return;
   if(!CAp::Assert(nrestarts>=0,__FUNCTION__+": NRestarts<0."))
      return;
//--- Initialize parameter Rep
   rep.m_RelCLSError=0;
   rep.m_AvgCE=0;
   rep.m_RMSError=0;
   rep.m_AvgError=0;
   rep.m_AvgRelError=0;
   rep.m_ngrad=0;
   rep.m_nhess=0;
   rep.m_ncholesky=0;
//--- Allocate
   s.m_subset.Resize(s.m_npoints);
   s.m_valsubset.Resize(s.m_npoints);
//--- Start training
//--- NOTE: ESessions is not initialized because MLPTrainEnsembleX
//---       needs uninitialized pool.
   sgrad=0;
   MLPTrainEnsembleX(s,ensemble,0,ensemble.m_ensemblesize,nrestarts,0,sgrad);
   rep.m_ngrad=sgrad;
//--- Calculate errors.
   CMLPE::MLPEAllErrorsX(ensemble,s.m_densexy,s.m_sparsexy,s.m_npoints,s.m_datatype,ensemble.m_network.m_dummyidx,0,s.m_npoints,0,tmprep);
   rep.m_RelCLSError=tmprep.m_RelCLSError;
   rep.m_AvgCE=tmprep.m_AvgCE;
   rep.m_RMSError=tmprep.m_RMSError;
   rep.m_AvgError=tmprep.m_AvgError;
   rep.m_AvgRelError=tmprep.m_AvgRelError;
  }
//+------------------------------------------------------------------+
//| Calculation of all types of errors                               |
//+------------------------------------------------------------------+
void CMLPE::MLPEAllErrors(CMLPEnsemble &ensemble,CMatrixDouble &xy,
                          const int npoints,double &relcls,
                          double &avgce,double &rms,
                          double &avg,double &avgrel)
  {
//--- create variables
   int i1_=0;
   int nin=0;
   int nout=0;
   int wcount=0;
//--- creating arrays
   CRowDouble buf;
   CRowDouble workx;
   CRowDouble y;
   CRowDouble dy;
//--- initialization
   relcls=0;
   avgce=0;
   rms=0;
   avg=0;
   avgrel=0;
//--- allocation
   CMLPBase::MLPProperties(ensemble.m_network,nin,nout,wcount);
   workx.Resize(nin);
   y.Resize(nout);
//--- check
   if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
     {
      //--- allocation
      dy.Resize(1);
      //--- function call
      CBdSS::DSErrAllocate(nout,buf);
     }
   else
     {
      //--- allocation
      dy.Resize(nout);
      //--- function call
      CBdSS::DSErrAllocate(-nout,buf);
     }
//--- calculation
   for(int i=0; i<npoints; i++)
     {
      workx=xy[i]+0;
      //--- function call
      MLPEProcess(ensemble,workx,y);
      //--- check
      if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
         dy.Set(0,xy.Get(i,nin));
      else
        {
         i1_=nin;
         for(int i_=0; i_<nout; i_++)
            dy.Set(i_,xy.Get(i,i_+i1_));
        }
      //--- function call
      CBdSS::DSErrAccumulate(buf,y,dy);
     }
//--- function call
   CBdSS::DSErrFinish(buf);
//--- change values
   relcls=buf[0];
   avgce=buf[1];
   rms=buf[2];
   avg=buf[3];
   avgrel=buf[4];
  }
//+------------------------------------------------------------------+
//| Internal bagging subroutine.                                     |
//+------------------------------------------------------------------+
void CMLPE::MLPEBaggingInternal(CMLPEnsemble &ensemble,CMatrixDouble &xy,
                                const int npoints,const double decay,
                                const int restarts,const double wstep,
                                const int maxits,const bool lmalgorithm,
                                int &info,CMLPReport &rep,CMLPCVReport &ooberrors)
  {
//--- create variables
   int    nin=0;
   int    nout=0;
   int    wcount=0;
   int    ccnt=0;
   int    pcnt=0;
   double v=0;
   int    i1_=0;
//--- creating arrays
   bool   s[];
   int    oobcntbuf[];
   double x[];
   double y[];
   double dy[];
   double dsbuf[];
//--- create matrix
   CMatrixDouble xys;
   CMatrixDouble oobbuf;
//--- objects of classes
   CMLPReport            tmprep;
   CMultilayerPerceptron network;
//--- initialization
   info=0;
//--- Test for inputs
   if((!lmalgorithm && wstep==0.0) && maxits==0)
     {
      info=-8;
      return;
     }
//--- check
   if(((npoints<=0 || restarts<1) || wstep<0.0) || maxits<0)
     {
      info=-1;
      return;
     }
//--- check
   if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
     {
      for(int i=0; i<npoints; i++)
        {
         //--- check
         if((int)MathRound(xy.Get(i,nin))<0 || (int)MathRound(xy.Get(i,nin))>=nout)
           {
            info=-2;
            return;
           }
        }
     }
//--- allocate temporaries
   info=2;
   rep.m_ngrad=0;
   rep.m_nhess=0;
   rep.m_ncholesky=0;
   ooberrors.m_RelCLSError=0;
   ooberrors.m_AvgCE=0;
   ooberrors.m_RMSError=0;
   ooberrors.m_AvgError=0;
   ooberrors.m_AvgRelError=0;
   CMLPBase::MLPProperties(ensemble.m_network,nin,nout,wcount);
//--- check
   if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
     {
      ccnt=nin+1;
      pcnt=nin;
     }
   else
     {
      ccnt=nin+nout;
      pcnt=nin+nout;
     }
//--- allocation
   xys.Resize(npoints,ccnt);
   ArrayResizeAL(s,npoints);
   oobbuf.Resize(npoints,nout);
   ArrayResizeAL(oobcntbuf,npoints);
   ArrayResize(x,nin);
   ArrayResize(y,nout);
//--- check
   if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
      ArrayResize(dy,1);
   else
      ArrayResize(dy,nout);
//--- initialization
   for(int i=0; i<npoints; i++)
     {
      for(int j=0; j<=nout-1; j++)
         oobbuf.Set(i,j,0);
     }
   for(int i=0; i<npoints; i++)
      oobcntbuf[i]=0;
//--- main bagging cycle
   for(int k=0; k<=ensemble.m_ensemblesize-1; k++)
     {
      //--- prepare dataset
      for(int i=0; i<npoints; i++)
         s[i]=false;
      for(int i=0; i<npoints; i++)
        {
         int j=CMath::RandomInteger(npoints);
         s[j]=true;
         for(int i_=0; i_<=ccnt-1; i_++)
            xys.Set(i,i_,xy[j][i_]);
        }
      //--- train
      if(lmalgorithm)
         CMLPTrain::MLPTrainLM(network,xys,npoints,decay,restarts,info,tmprep);
      else
         CMLPTrain::MLPTrainLBFGS(network,xys,npoints,decay,restarts,wstep,maxits,info,tmprep);
      //--- check
      if(info<0)
         return;
      //--- save results
      rep.m_ngrad=rep.m_ngrad+tmprep.m_ngrad;
      rep.m_nhess=rep.m_nhess+tmprep.m_nhess;
      rep.m_ncholesky=rep.m_ncholesky+tmprep.m_ncholesky;
      //--- copy
      i1_=-(k*wcount);
      for(int i_=k*wcount; i_<=(k+1)*wcount-1; i_++)
         ensemble.m_weights.Set(i_,network.m_weights[i_+i1_]);
      //--- copy
      i1_=-(k*pcnt);
      for(int i_=k*pcnt; i_<=(k+1)*pcnt-1; i_++)
         ensemble.m_columnmeans.Set(i_,network.m_columnmeans[i_+i1_]);
      //--- copy
      i1_=-(k*pcnt);
      for(int i_=k*pcnt; i_<=(k+1)*pcnt-1; i_++)
         ensemble.m_columnsigmas.Set(i_,network.m_columnsigmas[i_+i1_]);
      //--- OOB estimates
      for(int i=0; i<npoints; i++)
        {
         //--- check
         if(!s[i])
           {
            for(int i_=0; i_<=nin-1; i_++)
               x[i_]=xy.Get(i,i_);
            //--- function call
            CMLPBase::MLPProcess(network,x,y);
            //--- change value
            for(int i_=0; i_<=nout-1; i_++)
               oobbuf.Set(i,i_,oobbuf[i][i_]+y[i_]);
            oobcntbuf[i]=oobcntbuf[i]+1;
           }
        }
     }
//--- OOB estimates
   if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
     {
      //--- function call
      CBdSS::DSErrAllocate(nout,dsbuf);
     }
   else
     {
      //--- function call
      CBdSS::DSErrAllocate(-nout,dsbuf);
     }
   for(int i=0; i<npoints; i++)
     {
      //--- check
      if(oobcntbuf[i]!=0)
        {
         v=1.0/(double)oobcntbuf[i];
         for(int i_=0; i_<=nout-1; i_++)
            y[i_]=v*oobbuf[i][i_];
         //--- check
         if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
            dy[0]=xy[i][nin];
         else
           {
            i1_=nin;
            for(int i_=0; i_<=nout-1; i_++)
               dy[i_]=v*xy[i][i_+i1_];
           }
         //--- function call
         CBdSS::DSErrAccumulate(dsbuf,y,dy);
        }
     }
//--- function call
   CBdSS::DSErrFinish(dsbuf);
//--- change values
   ooberrors.m_RelCLSError=dsbuf[0];
   ooberrors.m_AvgCE=dsbuf[1];
   ooberrors.m_RMSError=dsbuf[2];
   ooberrors.m_AvgError=dsbuf[3];
   ooberrors.m_AvgRelError=dsbuf[4];
  }
//+------------------------------------------------------------------+
//| Principal components analysis                                    |
//+------------------------------------------------------------------+
class CPCAnalysis
  {
public:
   static void       PCABuildBasis(CMatrixDouble &x,const int npoints,const int nvars,int &info,double &s2[],CMatrixDouble &v);
   static void       PCABuildBasis(CMatrixDouble &x,const int npoints,const int nvars,int &info,CRowDouble &s2,CMatrixDouble &v);
   static void       PCATruncatedSubSpace(CMatrixDouble &x,int npoints,int nvars,int nneeded,double eps,int maxits,CRowDouble &s2,CMatrixDouble &v);
   static void       PCATruncatedSubSpaceSparse(CSparseMatrix &x,int npoints,int nvars,int nneeded,double eps,int maxits,CRowDouble &s2,CMatrixDouble &v);

  };
//+------------------------------------------------------------------+
//| Principal components analysis                                    |
//| Subroutine builds orthogonal basis where first axis corresponds  |
//| to direction with maximum variance, second axis maximizes        |
//| variance in subspace orthogonal to first axis and so on.         |
//| It should be noted that, unlike LDA, PCA does not use class      |
//| labels.                                                          |
//| INPUT PARAMETERS:                                                |
//|     X           -   dataset, array[0..NPoints-1,0..NVars-1].     |
//|                     matrix contains ONLY INDEPENDENT VARIABLES.  |
//|     NPoints     -   dataset size, NPoints>=0                     |
//|     NVars       -   number of independent variables, NVars>=1    |
//| OUTPUT PARAMETERS:                                               |
//|     Info        -   return code:                                 |
//|                     * -4, if SVD subroutine haven't converged    |
//|                     * -1, if wrong parameters has been passed    |
//|                           (NPoints<0, NVars<1)                   |
//|                     *  1, if task is solved                      |
//|     S2          -   array[0..NVars-1]. variance values           |
//|                     corresponding to basis vectors.              |
//|     V           -   array[0..NVars-1,0..NVars-1]                 |
//|                     matrix, whose columns store basis vectors.   |
//+------------------------------------------------------------------+
void CPCAnalysis::PCABuildBasis(CMatrixDouble &x,const int npoints,
                                const int nvars,int &info,
                                double &s2[],
                                CMatrixDouble &v)
  {
   CRowDouble S2=s2;
   PCABuildBasis(x,npoints,nvars,info,S2,v);
   S2.ToArray(s2);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CPCAnalysis::PCABuildBasis(CMatrixDouble &x,const int npoints,
                                const int nvars,int &info,
                                CRowDouble &s2,
                                CMatrixDouble &v)
  {
//--- creating arrays
   vector<double> m;
//--- create matrix
   CMatrixDouble a;
   CMatrixDouble u;
   CMatrixDouble vt;
//--- initialization
   info=0;
//--- Check input data
   if(npoints<0 || nvars<1)
     {
      info=-1;
      return;
     }
//--- change value
   info=1;
//--- Special case: NPoints=0
   if(npoints==0)
     {
      //--- initialization
      s2=vector<double>::Zeros(nvars);
      v=matrix<double>::Eye(nvars,nvars);
      //--- exit the function
      return;
     }
//--- Calculate means
   CMatrixDouble mx=x;
   mx.Resize(npoints,nvars);
   m=mx.Mean(0);
//--- Center,apply SVD,prepare output
   a=matrix<double>::Zeros(MathMax(npoints,nvars),nvars);
//--- calculation
   for(int i=0; i<npoints; i++)
      a.Row(i,mx[i]-m);
//--- check
   if(!CSingValueDecompose::RMatrixSVD(a,MathMax(npoints,nvars),nvars,0,1,2,s2,u,vt))
     {
      info=-4;
      return;
     }
//--- check
   if(npoints!=1)
      s2=s2.Pow(2.0)/(npoints-1);
//--- function call
   v=vt.Transpose()+0;
  }
//+------------------------------------------------------------------+
//| Principal components analysis                                    |
//| This function performs truncated PCA, i.e. returns just a few    |
//| most important directions.                                       |
//| Internally it uses iterative eigensolver which is very efficient |
//| when only a minor fraction of full basis is required. Thus, if   |
//| you need full basis, it is better to use pcabuildbasis() function|
//| It should be noted that, unlike LDA, PCA does not use class      |
//| labels.                                                          |
//| INPUT PARAMETERS:                                                |
//|   X        -  dataset, array[0..NPoints-1,0..NVars-1] matrix     |
//|               contains ONLY INDEPENDENT VARIABLES.               |
//|   NPoints  -  dataset size, NPoints>=0                           |
//|   NVars    -  number of independent variables, NVars>=1          |
//|   NNeeded  -  number of requested components, in [1,NVars] range;|
//|               this function is efficient only for NNeeded<<NVars.|
//|   Eps      -  desired precision of vectors returned; underlying  |
//|               solver will stop iterations as soon as absolute    |
//|               error in corresponding singular values reduces to  |
//|               roughly eps*MAX(lambda[]), with lambda[] being     |
//|               array of eigen values.                             |
//|               Zero value means that algorithm performs number of |
//|               iterations specified by maxits parameter, without  |
//|               paying attention to precision.                     |
//|   MaxIts   -  number of iterations performed by subspace         |
//|               iteration method. Zero value means that no limit on|
//|               iteration count is placed (eps-based stopping      |
//|               condition is used).                                |
//| OUTPUT PARAMETERS:                                               |
//|   S2       -  array[NNeeded]. Variance values corresponding to   |
//|               basis vectors.                                     |
//|   V        -  array[NVars,NNeeded] matrix, whose columns store   |
//|               basis vectors.                                     |
//| NOTE: passing eps=0 and maxits=0 results in small eps being      |
//|      selected as stopping condition. Exact value of automatically|
//|      selected eps is version-dependent.                          |
//+------------------------------------------------------------------+
void CPCAnalysis::PCATruncatedSubSpace(CMatrixDouble &x,
                                       int npoints,
                                       int nvars,
                                       int nneeded,
                                       double eps,
                                       int maxits,
                                       CRowDouble &s2,
                                       CMatrixDouble &v)
  {
//--- check
   if(!CAp::Assert(npoints>=0,__FUNCTION__+": npoints<0"))
      return;
   if(!CAp::Assert(nvars>=1,__FUNCTION__+": nvars<1"))
      return;
   if(!CAp::Assert(nneeded>0,__FUNCTION__+": nneeded<1"))
      return;
   if(!CAp::Assert(nneeded<=nvars,__FUNCTION__+": nneeded>nvars"))
      return;
   if(!CAp::Assert(maxits>=0,__FUNCTION__+": maxits<0"))
      return;
   if(!CAp::Assert(MathIsValidNumber(eps) && (double)(eps)>=0.0,__FUNCTION__+": eps<0 or is not finite"))
      return;
   if(!CAp::Assert((int)CAp::Rows(x)>=npoints,__FUNCTION__+": rows(x)<npoints"))
      return;
   if(!CAp::Assert((int)CAp::Cols(x)>=nvars || npoints==0,__FUNCTION__+": cols(x)<nvars"))
      return;
//--- create variables
   CMatrixDouble a,b;
   vector<double> means;
   int    k=0;
   double vv=0;
   CEigSubSpaceState solver;
   CEigSubSpaceReport rep;
//--- Special case: NPoints=0
   if(npoints==0)
     {
      s2=vector<double>::Zeros(nneeded);
      v=matrix<double>::Eye(nvars,nneeded);
      return;
     }
//--- Center matrix
   a=x;
   a.Resize(npoints,nvars);
   means=a.Mean(0);
   for(int i=0; i<npoints; i++)
     {
      //--- check
      if(!CAp::Assert(a.Row(i,x[i]-means),__FUNCTION__+":error update row " + IntegerToString(i)))
         return;
     }
//--- Find eigenvalues with subspace iteration solver
   CEigenVDetect::EigSubSpaceCreate(nvars,nneeded,solver);
   CEigenVDetect::EigSubSpaceSetCond(solver,eps,maxits);
   CEigenVDetect::EigSubSpaceOOCStart(solver,0);
   while(CEigenVDetect::EigSubSpaceOOCContinue(solver))
     {
      //--- check
      if(!CAp::Assert(solver.m_RequestType==0,__FUNCTION__+": integrity check failed"))
         return;
      k=solver.m_RequestSize;
      CApServ::RMatrixSetLengthAtLeast(b,npoints,k);
      CAblas::RMatrixGemm(npoints,k,nvars,1.0,a,0,0,0,solver.m_X,0,0,0,0.0,b,0,0);
      CAblas::RMatrixGemm(nvars,k,npoints,1.0,a,0,0,1,b,0,0,0,0.0,solver.m_AX,0,0);
     }
   CEigenVDetect::EigSubSpaceOOCStop(solver,s2,v,rep);
   if(npoints!=1)
      s2/=(npoints-1);
  }
//+------------------------------------------------------------------+
//| Sparse truncated principal components analysis                   |
//| This function performs sparse truncated PCA, i.e. returns just a |
//| few most important principal components for a sparse input X.    |
//| Internally it uses iterative eigensolver which is very efficient |
//| when only a minor fraction of full basis is required.            |
//| It should be noted that, unlike LDA, PCA does not use class      |
//| labels.                                                          |
//| INPUT PARAMETERS:                                                |
//|   X        -  sparse dataset, sparse npoints*nvars matrix. It is |
//|               recommended to use CRS sparse storage format;      |
//|               non-CRS input will be internally converted to CRS. |
//|               Matrix contains ONLY INDEPENDENT VARIABLES,  and   |
//|               must be EXACTLY npoints*nvars.                     |
//|   NPoints  -  dataset size, NPoints>=0                           |
//|   NVars    -  number of independent variables, NVars>=1          |
//|   NNeeded  -  number of requested components, in [1,NVars] range;|
//|               this function is efficient only for NNeeded<<NVars.|
//|   Eps      -  desired precision of vectors returned; underlying  |
//|               solver will stop iterations as soon as absolute    |
//|               error in corresponding singular values reduces  to |
//|               roughly eps*MAX(lambda[]), with lambda[] being     |
//|               array of eigen values.                             |
//|               Zero value means that algorithm performs number of |
//|               iterations specified by maxits parameter, without  |
//|               paying attention to precision.                     |
//|   MaxIts   -  number of iterations performed by subspace         |
//|               iteration method. Zero value means that no limit on|
//|               iteration count is placed (eps-based stopping      |
//|               condition is used).                                |
//| OUTPUT PARAMETERS:                                               |
//|   S2       -  array[NNeeded]. Variance values corresponding to   |
//|               basis vectors.                                     |
//|   V        -  array[NVars,NNeeded] matrix, whose columns store   |
//|               basis vectors.                                     |
//| NOTE: passing eps=0 and maxits=0 results in small eps being      |
//|       selected as a stopping condition. Exact value of           |
//|       automatically selected eps is version-dependent.           |
//| NOTE: zero MaxIts is silently replaced by some reasonable value  |
//|       which prevents eternal loops (possible when inputs are     |
//|       degenerate and too stringent stopping criteria are         |
//|       specified). In current version it is 50+2*NVars.           |
//+------------------------------------------------------------------+
void CPCAnalysis::PCATruncatedSubSpaceSparse(CSparseMatrix &x,
                                             int npoints,
                                             int nvars,
                                             int nneeded,
                                             double eps,
                                             int maxits,
                                             CRowDouble &s2,
                                             CMatrixDouble &v)
  {
//--- check
   if(!CAp::Assert(npoints>=0,__FUNCTION__+": npoints<0"))
      return;
   if(!CAp::Assert(nvars>=1,__FUNCTION__+": nvars<1"))
      return;
   if(!CAp::Assert(nneeded>0,__FUNCTION__+": nneeded<1"))
      return;
   if(!CAp::Assert(nneeded<=nvars,__FUNCTION__+": nneeded>nvars"))
      return;
   if(!CAp::Assert(maxits>=0,__FUNCTION__+": maxits<0"))
      return;
   if(!CAp::Assert(MathIsValidNumber(eps) && eps>=0.0,__FUNCTION__+": eps<0 or is not finite"))
      return;
//--- create variables
   double vv=0;
//--- create arrays
   CSparseMatrix xcrs;
   CRowDouble b1;
   CRowDouble c1;
   CRowDouble z1;
   CRowDouble means;
   CEigSubSpaceState solver;
   CEigSubSpaceReport rep;
//--- Special case: NPoints=0
   if(npoints==0)
     {
      s2=vector<double>::Zeros(nneeded);
      v=matrix<double>::Identity(nvars,nneeded);
      return;
     }
//--- check
   if(!CAp::Assert(CSparse::SparseGetNRows(x)==npoints,__FUNCTION__+": rows(x)!=npoints"))
      return;
   if(!CAp::Assert(CSparse::SparseGetNCols(x)==nvars,__FUNCTION__+": cols(x)!=nvars"))
      return;
//--- If input data are not in CRS format, perform conversion to CRS
   if(!CSparse::SparseIsCRS(x))
     {
      CSparse::SparseCopyToCRS(x,xcrs);
      PCATruncatedSubSpaceSparse(xcrs,npoints,nvars,nneeded,eps,maxits,s2,v);
      return;
     }
//--- Initialize parameters, prepare buffers
   if(eps==0.0 && maxits==0)
      eps=1.0E-6;
   if(maxits==0)
      maxits=50+2*nvars;
//--- Calculate mean values
   vv=1.0/(double)npoints;
   b1=vector<double>::Full(npoints,vv);
   CSparse::SparseMTV(x,b1,means);
//--- Find eigenvalues with subspace iteration solver
   CEigenVDetect::EigSubSpaceCreate(nvars,nneeded,solver);
   CEigenVDetect::EigSubSpaceSetCond(solver,eps,maxits);
   CEigenVDetect::EigSubSpaceOOCStart(solver,0);
   while(CEigenVDetect::EigSubSpaceOOCContinue(solver))
     {
      if(!CAp::Assert(solver.m_RequestType==0,__FUNCTION__+": integrity check failed"))
         return;
      for(int k=0; k<solver.m_RequestSize; k++)
        {
         //--- Calculate B1=(X-meansX)*Zk
         z1=solver.m_X.Col(k)+0;
         CSparse::SparseMV(x,z1,b1);
         vv=means.Dot(solver.m_X.Col(k)+0);
         b1-=vv;
         //--- Calculate (X-meansX)^T*B1
         CSparse::SparseMTV(x,b1,c1);
         vv=b1.Sum();
         solver.m_AX.Col(k,(c1.ToVector()-means*vv));
        }
     }
   CEigenVDetect::EigSubSpaceOOCStop(solver,s2,v,rep);
   if(npoints!=1)
      s2/=(npoints-1);
  }
//+------------------------------------------------------------------+
//| This structure is used to  store  temporaries  for               |
//| KMeansGenerateInternal function, so we will be able to reuse them|
//| during multiple subsequent calls.                                |
//+------------------------------------------------------------------+
struct CKmeansBuffers
  {
   CMatrixDouble     m_ct;
   CMatrixDouble     m_ctbest;
   CRowInt           m_xycbest;
   CRowInt           m_xycprev;
   CRowDouble        m_d2;
   CRowInt           m_csizes;
   CApBuff           m_initbuf;
   //---
                     CKmeansBuffers(void) {}
                    ~CKmeansBuffers(void) {}
   //---
   void              Copy(const CKmeansBuffers &obj);
   //--- overloading
   void              operator=(const CKmeansBuffers &obj) { Copy(obj); }
  };
//+------------------------------------------------------------------+
//| Copy                                                             |
//+------------------------------------------------------------------+
void CKmeansBuffers::Copy(const CKmeansBuffers &obj)
  {
   m_ct=obj.m_ct;
   m_ctbest=obj.m_ctbest;
   m_xycbest=obj.m_xycbest;
   m_xycprev=obj.m_xycprev;
   m_d2=obj.m_d2;
   m_csizes=obj.m_csizes;
   m_initbuf=obj.m_initbuf;
  }
//+------------------------------------------------------------------+
//| This structure is a clusterization engine.                       |
//| You should not try to access its fields directly.                |
//| Use ALGLIB functions in order to work with this object.          |
//+------------------------------------------------------------------+
struct CClusterizerState
  {
   int               m_NPoints;
   int               m_nfeatures;
   int               m_disttype;
   CMatrixDouble     m_xy;
   CMatrixDouble     m_d;
   int               m_ahcalgo;
   int               m_kmeansrestarts;
   int               m_kmeansmaxits;
   int               m_kmeansinitalgo;
   bool              m_kmeansdbgnoits;
   int               m_seed;
   CMatrixDouble     m_tmpd;
   CApBuff           m_distbuf;
   CKmeansBuffers    m_kmeanstmp;
   //---
                     CClusterizerState(void);
                    ~CClusterizerState(void) {}
   //---
   void              Copy(const CClusterizerState &obj);
   //--- overloading
   void              operator=(const CClusterizerState &obj) { Copy(obj); }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CClusterizerState::CClusterizerState(void)
  {
   m_NPoints=0;
   m_nfeatures=0;
   m_disttype=0;
   m_ahcalgo=0;
   m_kmeansrestarts=0;
   m_kmeansmaxits=0;
   m_kmeansinitalgo=0;
   m_kmeansdbgnoits=0;
   m_seed=0;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CClusterizerState::Copy(const CClusterizerState &obj)
  {
   m_NPoints=obj.m_NPoints;
   m_nfeatures=obj.m_nfeatures;
   m_disttype=obj.m_disttype;
   m_xy=obj.m_xy;
   m_d=obj.m_d;
   m_ahcalgo=obj.m_ahcalgo;
   m_kmeansrestarts=obj.m_kmeansrestarts;
   m_kmeansmaxits=obj.m_kmeansmaxits;
   m_kmeansinitalgo=obj.m_kmeansinitalgo;
   m_kmeansdbgnoits=obj.m_kmeansdbgnoits;
   m_seed=obj.m_seed;
   m_tmpd=obj.m_tmpd;
   m_distbuf=obj.m_distbuf;
   m_kmeanstmp=obj.m_kmeanstmp;
  }
//+------------------------------------------------------------------+
//| This structure is used to store results of the agglomerative     |
//| hierarchical clustering (AHC).                                   |
//| Following information is returned:                               |
//|   * TerminationType - completion code:                           |
//|   * 1   for successful completion of algorithm                   |
//|   * -5  inappropriate combination of  clustering  algorithm  and |
//|         distance function was used. As for now, it is possible   |
//|         only when Ward's method is called for dataset with       |
//|         non-Euclidean distance function.                         |
//| In case negative completion code is returned, other fields of    |
//| report structure are invalid and should not be used.             |
//|   * NPoints contains number of points in the original dataset    |
//|   * Z contains information about merges performed  (see below).  |
//|     Z contains indexes from the original (unsorted) dataset and  |
//|     it can be used when you need to know what points were merged.|
//|     However, it is not convenient when you want to build a       |
//|     dendrograd (see below).                                      |
//|   * if you want to build dendrogram, you can use Z, but it is not|
//|     good option, because Z contains indexes from unsorted        |
//|     dataset. Dendrogram built from such dataset is likely to     |
//|     have intersections. So, you have to reorder you points before|
//|     building dendrogram.                                         |
//| Permutation which reorders point is returned in P. Another       |
//| representation of merges, which is more convenient for dendorgram|
//| construction, is returned in PM.                                 |
//|   * more information on format of Z, P and PM can be found below |
//|     and in the examples from ALGLIB Reference Manual.            |
//| FORMAL DESCRIPTION OF FIELDS:                                    |
//|   NPoints     -  number of points                                |
//|   Z           -  array[NPoints-1,2], contains indexes of clusters|
//|                  linked in pairs to form clustering tree. I-th   |
//|                  row corresponds to I-th merge:                  |
//|               * Z[I,0] - index of the first cluster to merge     |
//|               * Z[I,1] - index of the second cluster to merge    |
//|               * Z[I,0]<Z[I,1]                                    |
//|               * clusters are numbered from 0 to 2*NPoints-2, with|
//|                 indexes from 0 to NPoints-1 corresponding to     |
//|                 points of the original dataset, and indexes from |
//|                 NPoints to 2*NPoints-2 correspond to clusters    |
//|                 generated by subsequent merges (I-th row of Z    |
//|                 creates cluster with index NPoints+I).           |
//| IMPORTANT: indexes in Z[] are indexes in the ORIGINAL, unsorted  |
//|            dataset. In addition to Z algorithm outputs           |
//|            permutation which rearranges points in such way that  |
//|            subsequent merges are performed on adjacent points    |
//|            (such order is needed if you want to build            |
//|            dendrogram). However, indexes in Z are related to     |
//|            original, unrearranged sequence of points.            |
//|   P        -  array[NPoints], permutation which reorders points  |
//|               for dendrogram construction. P[i] contains index   |
//|               of the position where we should move I-th point    |
//|               of the original dataset in order to apply merges   |
//|               PZ/PM.                                             |
//|   PZ       -  same as Z, but for permutation of points given by P|
//|               The only thing which changed are indexes of the    |
//|               original points; indexes of clusters remained same.|
//|   MergeDist-  array[NPoints-1], contains distances between       |
//|               clusters being merged (MergeDist[i] correspond to  |
//|               merge stored in Z[i,...]):                         |
//|               * CLINK, SLINK and average linkage algorithms      |
//|                 report "raw", unmodified distance metric.        |
//|               * Ward's method reports weighted intra-cluster     |
//|                 variance, which is equal to                      |
//|                 ||Ca-Cb||^2 * Sa*Sb/(Sa+Sb).                     |
//|                 Here A and B are clusters being merged, Ca is a  |
//|                 center of A, Cb is a center of B, Sa is a size   |
//|                 of A, Sb is a size of B.                         |
//|   PM       -  array[NPoints-1,6], another representation of      |
//|               merges, which is suited for dendrogram construction|
//|               It deals with rearranged points (permutation P is  |
//|               applied) and represents merges in a form which     |
//|               different from one used by Z. For each I from 0 to |
//|               NPoints-2, I-th row of PM represents merge         |
//|               performed on two clusters C0 and C1. Here:         |
//|               * C0 contains points with indexes PM[I,0]...PM[I,1]|
//|               * C1 contains points with indexes PM[I,2]...PM[I,3]|
//|               * indexes stored in PM are given for dataset sorted|
//|                 according to permutation P                       |
//|               * PM[I,1]=PM[I,2]-1 (only adjacent clusters are    |
//|                 merged)                                          |
//|               * PM[I,0]<=PM[I,1], PM[I,2]<=PM[I,3], i.e. both    |
//|                 clusters contain at least one point              |
//|              *heights of "subdendrograms" corresponding to     |
//|                 C0/C1 are stored in PM[I,4] and PM[I,5].         |
//|                 Subdendrograms corresponding to single-point     |
//|                 clusters have height=0. Dendrogram of the merge  |
//|                 result has height H=max(H0,H1)+1.                |
//| NOTE: there is one-to-one correspondence between merges described|
//|       by Z and PM. I-th row of Z describes same merge of clusters|
//|       as I-th row of PM, with "left" cluster from Z corresponding|
//|       to the "left" one from PM.                                 |
//+------------------------------------------------------------------+
struct CAHCReport
  {
   int               m_terminationtype;
   int               m_NPoints;
   CRowInt           m_p;
   CMatrixInt        m_z;
   CMatrixInt        m_pz;
   CMatrixInt        m_pm;
   CRowDouble        m_mergedist;
   //---
                     CAHCReport(void) { m_terminationtype=0; m_NPoints=0; }
                    ~CAHCReport(void) {}
   //---
   void              Copy(const CAHCReport &obj);
   //--- overloading
   void              operator=(const CAHCReport &obj) { Copy(obj); }
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CAHCReport::Copy(const CAHCReport &obj)
  {
   m_terminationtype=obj.m_terminationtype;
   m_NPoints=obj.m_NPoints;
   m_p=obj.m_p;
   m_z=obj.m_z;
   m_pz=obj.m_pz;
   m_pm=obj.m_pm;
   m_mergedist=obj.m_mergedist;
  }
//+------------------------------------------------------------------+
//| This structure is used to store results of the k-means clustering|
//| algorithm.                                                       |
//| Following information is always returned:                        |
//|   * NPoints contains number of points in the original dataset    |
//|   * TerminationType contains completion code, negative on        |
//|     failure, positive on success                                 |
//|   * K contains number of clusters                                |
//| For positive TerminationType we return:                          |
//|   * NFeatures contains number of variables in the original       |
//|     dataset                                                      |
//|   * C, which contains centers found by algorithm                 |
//|   * CIdx, which maps points of the original dataset to clusters  |
//| FORMAL DESCRIPTION OF FIELDS:                                    |
//|   NPoints     -  number of points, >=0                           |
//|   NFeatures   -  number of variables, >=1                        |
//|   TerminationType completion code:                               |
//|         * -5 if distance type is anything different from         |
//|              Euclidean metric                                    |
//|         * -3 for degenerate dataset: a) less than K distinct     |
//|              points, b) K=0 for non-empty dataset.               |
//|         * +1 for successful completion                           |
//|   K        -  number of clusters                                 |
//|   C        -  array[K,NFeatures], rows of the array store centers|
//|   CIdx     -  array[NPoints], which contains cluster indexes     |
//|   IterationsCount actual number of iterations performed by       |
//|               clusterizer. If algorithm performed more than one  |
//|               random restart, total number of iterations is      |
//|               returned.                                          |
//|   Energy  - merit function, "energy", sum of squared deviations|
//|               from cluster centers                               |
//+------------------------------------------------------------------+
struct CKmeansReport
  {
   int               m_NPoints;
   int               m_nfeatures;
   int               m_terminationtype;
   int               m_iterationscount;
   double            m_energy;
   int               m_k;
   CMatrixDouble     m_c;
   CRowInt           m_cidx;
   //---
                     CKmeansReport(void);
                    ~CKmeansReport(void) {}
   //---
   void              Copy(const CKmeansReport &obj);
   //--- overloading
   void              operator=(const CKmeansReport &obj) { Copy(obj); }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CKmeansReport::CKmeansReport(void)
  {
   m_NPoints=0;
   m_nfeatures=0;
   m_terminationtype=0;
   m_iterationscount=0;
   m_energy=0;
   m_k=0;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CKmeansReport::Copy(const CKmeansReport &obj)
  {
   m_NPoints=obj.m_NPoints;
   m_nfeatures=obj.m_nfeatures;
   m_terminationtype=obj.m_terminationtype;
   m_iterationscount=obj.m_iterationscount;
   m_energy=obj.m_energy;
   m_k=obj.m_k;
   m_c=obj.m_c;
   m_cidx=obj.m_cidx;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CClustering
  {
public:
   //--- constants
   static const int  m_kmeansblocksize;
   static const int  m_kmeansparalleldim;
   static const int  m_kmeansparallelk;
   static const double m_complexitymultiplier;

   static void       ClusterizerCreate(CClusterizerState &s);
   static void       ClusterizerSetPoints(CClusterizerState &s,CMatrixDouble &xy,int npoints,int nfeatures,int disttype);
   static void       ClusterizerSetDistances(CClusterizerState &s,CMatrixDouble &d,int npoints,bool IsUpper);
   static void       ClusterizerSetAHCAlgo(CClusterizerState &s,int algo);
   static void       ClusterizerSetKMeansLimits(CClusterizerState &s,int restarts,int maxits);
   static void       ClusterizerSetKMeansInit(CClusterizerState &s,int initalgo);
   static void       ClusterizerSetSeed(CClusterizerState &s,int seed);
   static void       ClusterizerRunAHC(CClusterizerState &s,CAHCReport &rep);
   static void       ClusterizerRunKMeans(CClusterizerState &s,int k,CKmeansReport &rep);
   static void       ClusterizerGetDistances(CMatrixDouble &xy,int npoints,int nfeatures,int disttype,CMatrixDouble &d);
   static void       ClusterizerGetDistancesBuf(CApBuff &buf,CMatrixDouble &xy,int npoints,int nfeatures,int disttype,CMatrixDouble &d);
   static void       ClusterizerGetKClusters(CAHCReport &rep,int k,CRowInt &cidx,CRowInt &cz);
   static void       ClusterizerSeparatedByDist(CAHCReport &rep,double r,int &k,CRowInt &cidx,CRowInt &cz);
   static void       ClusterizerSeparatedByCorr(CAHCReport &rep,double r,int &k,CRowInt &cidx,CRowInt &cz);
   static void       KMeansGenerateInternal(CMatrixDouble &xy,int npoints,int nvars,int k,int initalgo,int seed,int maxits,
                                            int restarts,bool kmeansdbgnoits,int &info,int &iterationscount,CMatrixDouble &ccol,
                                            bool needccol,CMatrixDouble &crow,bool needcrow,CRowInt &xyc,double &energy,CKmeansBuffers &buf);
   static void       KMeansUpdateDistances(CMatrixDouble &xy,int idx0,int idx1,int nvars,CMatrixDouble &ct,int cidx0,int cidx1,CRowInt &xyc,CRowDouble &xydist2);

private:
   static void       SelectInitialCenters(CMatrixDouble &xy,int npoints,int nvars,int initalgo,CHighQualityRandState &rs,int k,CMatrixDouble &ct,CApBuff &initbuf);
   static bool       FixCenters(CMatrixDouble &xy,int npoints,int nvars,CMatrixDouble &ct,int k,CApBuff &initbuf);
   static void       ClusterizerRunAHCInternal(CClusterizerState &s,CMatrixDouble &d,CAHCReport &rep);
   static void       EvaluateDistanceMatrixRec(CMatrixDouble &xy,int nfeatures,int disttype,CMatrixDouble &d,int i0,int i1,int j0,int j1);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
const int CClustering::m_kmeansblocksize=32;
const int CClustering::m_kmeansparalleldim=8;
const int CClustering::m_kmeansparallelk=4;
const double CClustering::m_complexitymultiplier=1.0;
//+------------------------------------------------------------------+
//| This function initializes clusterizer object. Newly initialized  |
//| object is empty, i.e. it does not contain dataset. You should    |
//| use it as follows:                                               |
//|   1. creation                                                    |
//|   2. dataset is added with ClusterizerSetPoints()                |
//|   3. additional parameters are set                               |
//|   3. clusterization is performed with one of the clustering      |
//|      functions                                                   |
//+------------------------------------------------------------------+
void CClustering::ClusterizerCreate(CClusterizerState &s)
  {
   s.m_NPoints=0;
   s.m_nfeatures=0;
   s.m_disttype=2;
   s.m_ahcalgo=0;
   s.m_kmeansrestarts=1;
   s.m_kmeansmaxits=0;
   s.m_kmeansinitalgo=0;
   s.m_kmeansdbgnoits=false;
   s.m_seed=1;
  }
//+------------------------------------------------------------------+
//| This function adds dataset to the clusterizer structure.         |
//| This function overrides all previous calls of                    |
//| ClusterizerSetPoints() or ClusterizerSetDistances().             |
//| INPUT PARAMETERS:                                                |
//|   S        -  clusterizer state, initialized by                  |
//|               ClusterizerCreate()                                |
//|   XY       -  array[NPoints,NFeatures], dataset                  |
//|   NPoints  -  number of points, >=0                              |
//|   NFeatures-  number of features, >=1                            |
//|   DistType -  distance function:                                 |
//|               *  0    Chebyshev distance  (L-inf norm)           |
//|               *  1    city block distance (L1 norm)              |
//|               *  2    Euclidean distance  (L2 norm), non-squared |
//|               * 10    Pearson correlation:                       |
//|                       dist(a,b) = 1-corr(a,b)                    |
//|               * 11    Absolute Pearson correlation:              |
//|                       dist(a,b) = 1-|corr(a,b)|                  |
//|               * 12    Uncentered Pearson correlation (cosine of  |
//|                       the angle): dist(a,b) = a'*b/(|a|*|b|)     |
//|               * 13    Absolute uncentered Pearson correlation    |
//|                       dist(a,b) = |a'*b|/(|a|*|b|)               |
//|               * 20    Spearman rank correlation:                 |
//|                       dist(a,b) = 1-rankcorr(a,b)                |
//|               * 21    Absolute Spearman rank correlation         |
//|                       dist(a,b) = 1-|rankcorr(a,b)|              |
//| NOTE 1: different distance functions have different performance  |
//|         penalty:                                                 |
//|         * Euclidean or Pearson correlation distances are         |
//|           the fastest ones                                       |
//|         * Spearman correlation distance function is a bit slower |
//|         * city block and Chebyshev distances are order           |
//|           of magnitude slower                                    |
//|         The reason behing difference in performance is that      |
//|         correlation-based distance functions are computed using  |
//|         optimized linear algebra kernels, while Chebyshev and    |
//|         city block distance functions are computed using simple  |
//|         nested loops with two branches at each iteration.        |
//| NOTE 2: different clustering algorithms have different           |
//|         limitations:                                             |
//|         * agglomerative hierarchical clustering algorithms may   |
//|           be used with any kind of distance metric               |
//|         * k-means++ clustering algorithm may be used only with   |
//|           Euclidean distance function                            |
//|         Thus, list of specific clustering algorithms you may use |
//|         depends on distance function you specify when you set    |
//|         your dataset.                                            |
//+------------------------------------------------------------------+
void CClustering::ClusterizerSetPoints(CClusterizerState &s,
                                       CMatrixDouble &xy,
                                       int npoints,
                                       int nfeatures,
                                       int disttype)
  {
//--- check
   if(!CAp::Assert(disttype==0 || disttype==1 || disttype==2 || disttype==10 || disttype==11 || 
                   disttype==12 || disttype==13 || disttype==20 || disttype==21,__FUNCTION__": incorrect DistType"))
      return;
   if(!CAp::Assert(npoints>=0,__FUNCTION__": NPoints<0"))
      return;
   if(!CAp::Assert(nfeatures>=1,__FUNCTION__": NFeatures<1"))
      return;
   if(!CAp::Assert(xy.Rows()>=npoints,__FUNCTION__": Rows(XY)<NPoints"))
      return;
   if(!CAp::Assert(xy.Cols()>=nfeatures,__FUNCTION__": Cols(XY)<NFeatures"))
      return;
   if(!CAp::Assert(CApServ::IsFiniteMatrix(xy,npoints,nfeatures),__FUNCTION__": XY contains NAN/INF"))
      return;

   s.m_NPoints=npoints;
   s.m_nfeatures=nfeatures;
   s.m_disttype=disttype;
   s.m_xy=xy;
   s.m_xy.Resize(npoints,nfeatures);
  }
//+------------------------------------------------------------------+
//| This function adds dataset given by distance matrix to the       |
//| clusterizer structure. It is important that dataset is not given |
//| explicitly - only distance matrix is given.                      |
//| This function overrides all previous calls  of                   |
//| ClusterizerSetPoints() or ClusterizerSetDistances().             |
//| INPUT PARAMETERS:                                                |
//|   S        -  clusterizer state, initialized by                  |
//|               ClusterizerCreate()                                |
//|   D        -  array[NPoints,NPoints], distance matrix given by   |
//|               its upper or lower triangle (main diagonal is      |
//|               ignored because its entries are expected to        |
//|               be zero).                                          |
//|   NPoints  -  number of points                                   |
//|   IsUpper  -  whether upper or lower triangle of D is given.     |
//| NOTE 1: different clustering algorithms have different           |
//|         limitations:                                             |
//|         * agglomerative hierarchical clustering algorithms may   |
//|           be used with any kind of distance metric, including    |
//|           one which is given by distance matrix                  |
//|         * k-means++ clustering algorithm may be used only with   |
//|           Euclidean distance function and explicitly given       |
//|           points - it can not be used with dataset given by      |
//|           distance matrix. Thus, if you call this function, you  |
//|           will be unable to use k-means clustering algorithm     |
//|           to process your problem.                               |
//+------------------------------------------------------------------+
void CClustering::ClusterizerSetDistances(CClusterizerState &s,
                                          CMatrixDouble &d,
                                          int npoints,
                                          bool IsUpper)
  {
//--- check
   if(!CAp::Assert(npoints>=0,__FUNCTION__": NPoints<0"))
      return;
   if(!CAp::Assert(d.Rows()>=npoints,__FUNCTION__": Rows(D)<NPoints"))
      return;
   if(!CAp::Assert(d.Cols()>=npoints,__FUNCTION__": Cols(D)<NPoints"))
      return;
//--- initialization
   s.m_NPoints=npoints;
   s.m_nfeatures=0;
   s.m_disttype=-1;
   s.m_d=d;
   s.m_d.Resize(npoints,npoints);
   s.m_d.Diag(vector<double>::Zeros(npoints));
   if(IsUpper)
      s.m_d=s.m_d.TriU()+0;
   else
      s.m_d=s.m_d.TriL()+0;
   s.m_d+=s.m_d.Transpose()+0;
   if(!CAp::Assert(CApServ::IsFiniteMatrix(s.m_d,npoints,npoints) || s.m_d.Min()>=0,__FUNCTION__": D contains infinite,NAN or negative elements"))
      return;
  }
//+------------------------------------------------------------------+
//| This function sets agglomerative hierarchical clustering         |
//| algorithm                                                        |
//| INPUT PARAMETERS:                                                |
//|   S     -  clusterizer state, initialized by ClusterizerCreate() |
//|   Algo  -  algorithm type:                                       |
//|            * 0     complete linkage(default algorithm)           |
//|            * 1     single linkage                                |
//|            * 2     unweighted average linkage                    |
//|            * 3     weighted average linkage                      |
//|            * 4     Ward's method                                 |
//| NOTE: Ward's method works correctly only with Euclidean distance,|
//|       that's why algorithm will return negative termination      |
//|       code(failure) for any other distance type.                 |
//| It is possible, however, to use this method with user - supplied |
//| distance matrix. It is your responsibility to pass one which was |
//| calculated with Euclidean distance function.                     |
//+------------------------------------------------------------------+
void CClustering::ClusterizerSetAHCAlgo(CClusterizerState &s,int algo)
  {
   if(!CAp::Assert(algo==0 || algo==1 || algo==2 || algo==3 || algo==4,__FUNCTION__": incorrect algorithm type"))
      return;
   s.m_ahcalgo=algo;
  }
//+------------------------------------------------------------------+
//| This  function  sets k-means properties:                         |
//|      number  of  restarts and maximum                            |
//|      number of iterations per one run.                           |
//| INPUT PARAMETERS:                                                |
//|   S        -  clusterizer state, initialized by                  |
//|               ClusterizerCreate()                                |
//|   Restarts -  restarts count, >= 1.                              |
//|               k-means++ algorithm performs several restarts      |
//|               and chooses best set of centers(one with minimum   |
//|               squared distance).                                 |
//|   MaxIts   -  maximum number of k-means iterations performed     |
//|               during one run. >= 0, zero value means that        |
//|               algorithm performs unlimited number of iterations. |
//+------------------------------------------------------------------+
void CClustering::ClusterizerSetKMeansLimits(CClusterizerState &s,
                                             int restarts,
                                             int maxits)
  {
   if(!CAp::Assert(restarts>=1,__FUNCTION__": Restarts<=0"))
      return;
   if(!CAp::Assert(maxits>=0,__FUNCTION__": MaxIts<0"))
      return;
   s.m_kmeansrestarts=restarts;
   s.m_kmeansmaxits=maxits;
  }
//+------------------------------------------------------------------+
//| This function sets k-means initialization algorithm. Several     |
//| different algorithms can be chosen, including k-means++.         |
//| INPUT PARAMETERS:                                                |
//|   S        -  clusterizer state, initialized by                  |
//|               ClusterizerCreate()                                |
//|   InitAlgo -  initialization algorithm:                          |
//|      * 0  automatic selection(different  versions  of  ALGLIB    |
//|           may select different algorithms)                       |
//|      * 1  random initialization                                  |
//|      * 2  k-means++ initialization(best  quality  of  initial    |
//|           centers, but long  non-parallelizable initialization   |
//|           phase with bad cache locality)                         |
//|     *3  "fast-greedy" algorithm with efficient, easy to        |
//|           parallelize initialization. Quality of initial centers |
//|           is somewhat worse than that of k-means++. This         |
//|           algorithm is a default one in the current version of   |
//|           ALGLIB.                                                |
//|     *-1 "debug" algorithm which always selects first K rows    |
//|           of dataset; this algorithm is used for debug purposes  |
//|           only. Do not use it in the industrial code!            |
//+------------------------------------------------------------------+
void CClustering::ClusterizerSetKMeansInit(CClusterizerState &s,
                                           int initalgo)
  {
   if(!CAp::Assert(initalgo>=-1 && initalgo<=3,__FUNCTION__": InitAlgo is incorrect"))
      return;
   s.m_kmeansinitalgo=initalgo;
  }
//+------------------------------------------------------------------+
//| This function sets seed which is used to initialize internal RNG.|
//| By default, deterministic seed is used - same for each run of    |
//| clusterizer. If you specify non-deterministic seed value, then   |
//| some algorithms which depend on random initialization(in current |
//| version : k-means) may return slightly different results after   |
//| each run.                                                        |
//| INPUT PARAMETERS:                                                |
//|   S        -  clusterizer state, initialized by                  |
//|               ClusterizerCreate()                                |
//|   Seed     -  seed:                                              |
//|               * positive values = use deterministic seed for each|
//|                 run of algorithms which depend on random         |
//|                 initialization                                   |
//|               * zero or negative values = use non-deterministic  |
//|                 seed                                             |
//+------------------------------------------------------------------+
void CClustering::ClusterizerSetSeed(CClusterizerState &s,int seed)
  {
   s.m_seed=seed;
  }
//+------------------------------------------------------------------+
//| This function performs agglomerative hierarchical clustering     |
//| NOTE: Agglomerative hierarchical clustering algorithm has two    |
//|       phases: distance matrix calculation and clustering itself. |
//| INPUT PARAMETERS:                                                |
//|   S        -  clusterizer state, initialized by                  |
//|               ClusterizerCreate()                                |
//| OUTPUT PARAMETERS:                                               |
//|   Rep      -  clustering results; see description of AHCReport   |
//|               structure for more information.                    |
//| NOTE 1: hierarchical clustering algorithms require large amounts |
//|         of memory. In particular, this implementation needs      |
//|         sizeof(double) *NPoints^2 bytes, which are used to store |
//|         distance matrix. In case we work with user - supplied    |
//|         matrix, this amount is multiplied by 2 (we have to store |
//|         original matrix and to work with its copy).              |
//|         For example, problem with 10000 points would require 800M|
//|         of RAM, even when working in a 1-dimensional space.      |
//+------------------------------------------------------------------+
void CClustering::ClusterizerRunAHC(CClusterizerState &s,
                                    CAHCReport &rep)
  {
//--- create variables
   int npoints=s.m_NPoints;
   int nfeatures=s.m_nfeatures;
//--- Fill Rep.NPoints, quick exit when NPoints<=1
   rep.m_NPoints=npoints;
   if(npoints==0)
     {
      rep.m_p.Resize(0);
      rep.m_z.Resize(0,0);
      rep.m_pz.Resize(0,0);
      rep.m_pm.Resize(0,0);
      rep.m_mergedist.Resize(0);
      rep.m_terminationtype=1;
      return;
     }
   if(npoints==1)
     {
      rep.m_p.Resize(1);
      rep.m_z.Resize(0,0);
      rep.m_pz.Resize(0,0);
      rep.m_pm.Resize(0,0);
      rep.m_mergedist.Resize(0);
      rep.m_p.Set(0,0);
      rep.m_terminationtype=1;
      return;
     }
//--- More than one point
   if(s.m_disttype==-1)
     {
      //--- Run clusterizer with user-supplied distance matrix
      ClusterizerRunAHCInternal(s,s.m_d,rep);
      return;
     }
   else
     {
      //--- Check combination of AHC algo and distance type
      if(s.m_ahcalgo==4 && s.m_disttype!=2)
        {
         rep.m_terminationtype=-5;
         return;
        }
      //--- Build distance matrix D.
      ClusterizerGetDistancesBuf(s.m_distbuf,s.m_xy,npoints,nfeatures,s.m_disttype,s.m_tmpd);
      //--- Run clusterizer
      ClusterizerRunAHCInternal(s,s.m_tmpd,rep);
      return;
     }
  }
//+------------------------------------------------------------------+
//| This function performs clustering by k-means++ algorithm.        |
//| You may change algorithm properties by calling:                  |
//|      * ClusterizerSetKMeansLimits() to change number of restarts |
//|        or iterations                                             |
//|      * ClusterizerSetKMeansInit() to change initialization       |
//|        algorithm                                                 |
//| By default, one restart and unlimited number of iterations are   |
//| used. Initialization algorithm is chosen automatically.          |
//| NOTE: k-means clustering algorithm has two phases: selection of  |
//|       initial centers and clustering itself.                     |
//| INPUT PARAMETERS:                                                |
//|   S        -  clusterizer state, initialized by                  |
//|               ClusterizerCreate()                                |
//|   K        -  number of clusters, K >= 0.                        |
//|               K can be zero only when algorithm is called for    |
//|               empty dataset, in this case completion code is set |
//|               to success(+1).                                    |
//|               If K = 0 and dataset size is non-zero, we can not  |
//|               meaningfully assign points to some center(there are|
//|               no centers because K = 0) and return -3 as         |
//|               completion code (failure).                         |
//| OUTPUT PARAMETERS:                                               |
//|   Rep      -  clustering results; see description of KMeansReport|
//|               structure for more information.                    |
//| NOTE 1: k-means clustering can be performed only for datasets    |
//|         with Euclidean distance function. Algorithm will return  |
//|         negative completion code in Rep.TerminationType in case  |
//|         dataset was added to clusterizer with DistType other     |
//|         than Euclidean (or dataset was specified by distance     |
//|         matrix instead of explicitly given points).              |
//| NOTE 2: by default, k-means uses non-deterministic seed to       |
//|         initialize RNG which is used to select initial centers.  |
//|         As result, each run of algorithm may return different    |
//|         values. If you  need  deterministic behavior, use        |
//|         ClusterizerSetSeed() function.                           |
//+------------------------------------------------------------------+
void CClustering::ClusterizerRunKMeans(CClusterizerState &s,
                                       int k,
                                       CKmeansReport &rep)
  {
//--- create variables
   CMatrixDouble dummy;
//--- check
   if(!CAp::Assert(k>=0,__FUNCTION__": K<0"))
      return;
//--- Incorrect distance type
   if(s.m_disttype!=2)
     {
      rep.m_NPoints=s.m_NPoints;
      rep.m_terminationtype=-5;
      rep.m_k=k;
      rep.m_iterationscount=0;
      rep.m_energy=0.0;
      return;
     }
//--- K>NPoints or (K=0 and NPoints>0)
   if(k>s.m_NPoints || (k==0 && s.m_NPoints>0))
     {
      rep.m_NPoints=s.m_NPoints;
      rep.m_terminationtype=-3;
      rep.m_k=k;
      rep.m_iterationscount=0;
      rep.m_energy=0.0;
      return;
     }
//--- No points
   if(s.m_NPoints==0)
     {
      rep.m_NPoints=0;
      rep.m_terminationtype=1;
      rep.m_k=k;
      rep.m_iterationscount=0;
      rep.m_energy=0.0;
      return;
     }
//--- Normal case:
//--- 1<=K<=NPoints, Euclidean distance
   rep.m_NPoints=s.m_NPoints;
   rep.m_nfeatures=s.m_nfeatures;
   rep.m_k=k;
   rep.m_NPoints=s.m_NPoints;
   rep.m_nfeatures=s.m_nfeatures;
   KMeansGenerateInternal(s.m_xy,s.m_NPoints,s.m_nfeatures,k,s.m_kmeansinitalgo,s.m_seed,s.m_kmeansmaxits,
                          s.m_kmeansrestarts,s.m_kmeansdbgnoits,rep.m_terminationtype,rep.m_iterationscount,dummy,
                          false,rep.m_c,true,rep.m_cidx,rep.m_energy,s.m_kmeanstmp);
  }
//+------------------------------------------------------------------+
//| This function returns distance matrix for dataset                |
//| INPUT PARAMETERS:                                                |
//|   XY       -  array[NPoints, NFeatures], dataset                 |
//|   NPoints  -  number of points, >= 0                             |
//|   NFeatures-  number of features, >= 1                           |
//|   DistType -  distance function:                                 |
//|               *  0    Chebyshev distance(L - inf norm)           |
//|               *  1    city block distance(L1 norm)               |
//|               *  2    Euclidean distance(L2 norm, non - squared) |
//|               * 10    Pearson correlation:                       |
//|                       dist(a, b) = 1 - corr(a, b)                |
//|               * 11    Absolute Pearson correlation:              |
//|                       dist(a, b) = 1 - |corr(a, b)|              |
//|               * 12    Uncentered Pearson correlation(cosine of   |
//|                       the angle): dist(a, b) = a'*b/(|a|*|b|)    |
//|               * 13    Absolute uncentered Pearson correlation    |
//|                       dist(a, b) = |a'*b|/(|a|*|b|)              |
//|               * 20    Spearman rank correlation:                 |
//|                       dist(a, b) = 1 - rankcorr(a, b)            |
//|               * 21    Absolute Spearman rank correlation         |
//|                       dist(a, b) = 1 - |rankcorr(a, b)|          |
//| OUTPUT PARAMETERS:                                               |
//|   D        -  array[NPoints, NPoints], distance matrix (full     |
//|               matrix is returned, with lower and upper triangles)|
//| NOTE: different distance functions have different performance    |
//|       penalty:                                                   |
//|      * Euclidean or Pearson correlation distances are the fastest|
//|        ones                                                      |
//|      * Spearman correlation distance function is a bit slower    |
//|      * city block and Chebyshev distances are order of magnitude |
//|        slower                                                    |
//| The reason behing difference in performance is that correlation -|
//| based distance functions are computed using optimized linear     |
//| algebra kernels, while Chebyshev and city block distance         |
//| functions are computed using simple nested loops with two        |
//| branches at each iteration.                                      |
//+------------------------------------------------------------------+
void CClustering::ClusterizerGetDistances(CMatrixDouble &xy,
                                          int npoints,
                                          int nfeatures,
                                          int disttype,
                                          CMatrixDouble &d)
  {
   CApBuff buf;

   d.Resize(0,0);
//--- check
   if(!CAp::Assert(nfeatures>=1,__FUNCTION__": NFeatures<1"))
      return;
   if(!CAp::Assert(npoints>=0,__FUNCTION__": NPoints<1"))
      return;
   if(!CAp::Assert(disttype==0 || disttype==1 || disttype==2 || disttype==10 || disttype==11 ||
                   disttype==12 || disttype==13 || disttype==20 || disttype==21,__FUNCTION__": incorrect DistType"))
      return;
   if(!CAp::Assert(xy.Rows()>=npoints,__FUNCTION__": Rows(XY)<NPoints"))
      return;
   if(!CAp::Assert(xy.Cols()>=nfeatures,__FUNCTION__": Cols(XY)<NFeatures"))
      return;
   if(!CAp::Assert(CApServ::IsFiniteMatrix(xy,npoints,nfeatures),__FUNCTION__": XY contains NAN/INF"))
      return;
   ClusterizerGetDistancesBuf(buf,xy,npoints,nfeatures,disttype,d);
  }
//+------------------------------------------------------------------+
//| Buffered version  of  ClusterizerGetDistances() which reuses     |
//| previously allocated space.                                      |
//+------------------------------------------------------------------+
void CClustering::ClusterizerGetDistancesBuf(CApBuff &buf,
                                             CMatrixDouble &xy,
                                             int npoints,
                                             int nfeatures,
                                             int disttype,
                                             CMatrixDouble &d)
  {
//--- create variables
   double v=0;
   double vv=0;
   double vr=0;
//--- check
   if(!CAp::Assert(nfeatures>=1,__FUNCTION__": NFeatures<1"))
      return;
   if(!CAp::Assert(npoints>=0,__FUNCTION__": NPoints<1"))
      return;
   if(!CAp::Assert(disttype==0 || disttype==1 || disttype==2 || disttype==10 || disttype==11 || disttype==12 ||
                   disttype==13 || disttype==20 || disttype==21,__FUNCTION__": incorrect DistType"))
      return;
   if(!CAp::Assert(xy.Rows()>=npoints,__FUNCTION__": Rows(XY)<NPoints"))
      return;
   if(!CAp::Assert(xy.Cols()>=nfeatures,__FUNCTION__": Cols(XY)<NFeatures"))
      return;
   if(!CAp::Assert(CApServ::IsFiniteMatrix(xy,npoints,nfeatures),__FUNCTION__": XY contains NAN/INF"))
      return;
//--- Quick exit
   if(npoints==0)
      return;
   if(npoints==1)
     {
      CApServ::RMatrixSetLengthAtLeast(d,1,1);
      d.Set(0,0,0);
      return;
     }
//--- Build distance matrix D.
   if(disttype==0 || disttype==1)
     {
      //--- Chebyshev or city-block distances:
      //--- * recursively calculate upper triangle (with main diagonal)
      //--- * copy it to the bottom part of the matrix
      CApServ::RMatrixSetLengthAtLeast(d,npoints,npoints);
      EvaluateDistanceMatrixRec(xy,nfeatures,disttype,d,0,npoints,0,npoints);
      CAblas::RMatrixEnforceSymmetricity(d,npoints,true);
      return;
     }
   if(disttype==2)
     {
      //--- Euclidean distance
      //--- NOTE: parallelization is done within RMatrixSYRK
      d.Resize(npoints,npoints);
      buf.m_rm0=xy;
      buf.m_rm0.Resize(npoints,nfeatures);
      buf.m_ra1=buf.m_rm0.Mean(0)+0;
      for(int i=0; i<npoints; i++)
         buf.m_rm0.Row(i,buf.m_rm0[i]-(buf.m_ra1+0));
      CAblas::RMatrixSyrk(npoints,nfeatures,1.0,buf.m_rm0,0,0,0,0.0,d,0,0,true);
      buf.m_ra0=d.Diag(0)+0;
      d.Diag(vector<double>::Zeros(npoints));
      for(int i=0; i<npoints; i++)
        {
         for(int j=i+1; j<npoints; j++)
           {
            v=MathSqrt(MathMax(buf.m_ra0[i]+buf.m_ra0[j]-2*d.Get(i,j),0.0));
            d.Set(i,j,v);
           }
        }
      CAblas::RMatrixEnforceSymmetricity(d,npoints,true);
      return;
     }
   if(disttype==10 || disttype==11)
     {
      //--- Absolute/nonabsolute Pearson correlation distance
      //--- NOTE: parallelization is done within PearsonCorrM, which calls RMatrixSYRK internally
      d.Resize(npoints,npoints);
      buf.m_rm0=xy;
      buf.m_rm0.Resize(npoints,nfeatures);
      buf.m_ra0=buf.m_rm0.Mean(1)+0;
      for(int i=0; i<npoints; i++)
         buf.m_rm0.Row(i,buf.m_rm0[i]-buf.m_ra0[i]);
      CAblas::RMatrixSyrk(npoints,nfeatures,1.0,buf.m_rm0,0,0,0,0.0,d,0,0,true);
      buf.m_ra0=d.Diag()+0;
      d.Diag(vector<double>::Zeros(npoints));
      for(int i=0; i<npoints; i++)
        {
         for(int j=i+1; j<npoints; j++)
           {
            v=d.Get(i,j)/MathSqrt(buf.m_ra0[i]*buf.m_ra0[j]);
            if(disttype==10)
               v=1-v;
            else
               v=1-MathAbs(v);
            v=MathMax(v,0.0);
            d.Set(i,j,v);
           }
        }
      CAblas::RMatrixEnforceSymmetricity(d,npoints,true);
      return;
     }
   if(disttype==12 || disttype==13)
     {
      //--- Absolute/nonabsolute uncentered Pearson correlation distance
      //--- NOTE: parallelization is done within RMatrixSYRK
      d.Resize(npoints,npoints);
      CAblas::RMatrixSyrk(npoints,nfeatures,1.0,xy,0,0,0,0.0,d,0,0,true);
      buf.m_ra0=d.Diag()+0;
      d.Diag(vector<double>::Zeros(npoints));
      for(int i=0; i<npoints; i++)
        {
         for(int j=i+1; j<npoints; j++)
           {
            v=d.Get(i,j)/MathSqrt(buf.m_ra0[i]*buf.m_ra0[j]);
            if(disttype==13)
               v=MathAbs(v);
            v=MathMin(v,1.0);
            d.Set(i,j,1-v);
           }
        }
      CAblas::RMatrixEnforceSymmetricity(d,npoints,true);
      return;
     }
   if(disttype==20 || disttype==21)
     {
      //--- Spearman rank correlation
      //--- NOTE: parallelization of correlation matrix is done within
      //---       PearsonCorrM, which calls RMatrixSYRK internally
      d.Resize(npoints,npoints);
      buf.m_rm0=xy;
      buf.m_rm0.Resize(npoints,nfeatures);
      CBaseStat::RankDataCentered(buf.m_rm0,npoints,nfeatures);
      CAblas::RMatrixSyrk(npoints,nfeatures,1.0,buf.m_rm0,0,0,0,0.0,d,0,0,true);
      buf.m_ra0=vector<double>::Zeros(npoints);
      for(int i=0; i<npoints; i++)
        {
         if(d.Get(i,i)>0.0)
            buf.m_ra0.Set(i,1.0/MathSqrt(d.Get(i,i)));
        }
      d.Diag(vector<double>::Zeros(npoints));
      for(int i=0; i<npoints; i++)
        {
         v=buf.m_ra0[i];
         for(int j=i+1; j<npoints; j++)
           {
            vv=d.Get(i,j)*v*buf.m_ra0[j];
            if(disttype==20)
               vr=1-vv;
            else
               vr=1-MathAbs(vv);
            if(vr<0.0)
               vr=0.0;
            d.Set(i,j,vr);
           }
        }
      CAblas::RMatrixEnforceSymmetricity(d,npoints,true);
      return;
     }
   if(!CAp::Assert(false))
      return;
  }
//+------------------------------------------------------------------+
//| This function takes as input clusterization report Rep, desired  |
//| clusters count K, and builds top K clusters from hierarchical    |
//| clusterization tree.                                             |
//| It returns assignment of points to clusters(array of cluster     |
//| indexes).                                                        |
//| INPUT PARAMETERS:                                                |
//|   Rep     -  report from ClusterizerRunAHC() performed on XY     |
//|   K       -   desired number of clusters, 1 <= K <= NPoints.     |
//|               K can be zero only when NPoints = 0.               |
//| OUTPUT PARAMETERS:                                               |
//|   CIdx    -   array[NPoints], I-th element contains cluster      |
//|               index(from 0 to K-1) for I-th point of the dataset.|
//|   CZ      -   array[K]. This array allows  to  convert  cluster  |
//|               indexes returned by this function to indexes used  |
//|               by Rep.Z. J-th cluster returned by this function   |
//|               corresponds to CZ[J]-th cluster stored in          |
//|               Rep.Z/PZ/PM. It is guaranteed that CZ[I] < CZ[I+1].|
//| NOTE: K clusters built by this subroutine are assumed to have no |
//|       hierarchy. Although they were obtained by manipulation with|
//|       top K nodes of dendrogram(i.e. hierarchical decomposition  |
//|       of dataset), this function does not return information     |
//|       about hierarchy. Each of the clusters stand on its own.    |
//| NOTE: Cluster indexes returned by this function does not         |
//|       correspond to indexes returned in Rep.Z/PZ/PM. Either you  |
//|       work with hierarchical representation of the dataset       |
//|       (dendrogram), or you work with "flat" representation       |
//|       returned by this function. Each of representations has its |
//|       own clusters indexing system(former uses [0,2*NPoints-2]), |
//|       while latter uses [0..K-1]), although it is possible to    |
//|       perform conversion from one system to another by means of  |
//|       CZ array, returned by this function, which allows you to   |
//|       convert indexes stored in CIdx to the numeration system    |
//|       used by Rep.Z.                                             |
//| NOTE: this subroutine is optimized for moderate values of K.     |
//|       Say, for K=5 it will perform many times faster than for    |
//|       K=100. Its worst - case performance is O(N*K), although in |
//|       average case it perform better (up to O(N*log(K))).        |
//+------------------------------------------------------------------+
void CClustering::ClusterizerGetKClusters(CAHCReport &rep,
                                          int k,
                                          CRowInt &cidx,
                                          CRowInt &cz)
  {
//--- create variables
   int  npoints=rep.m_NPoints;
   int  i0=0;
   int  i1=0;
   int  t=0;
   bool presentclusters[];
   CRowInt clusterindexes;
   CRowInt clustersizes;
   CRowInt tmpidx;

   cidx.Resize(0);
   cz.Resize(0);
//--- check
   if(!CAp::Assert(npoints>=0,__FUNCTION__": internal error in Rep integrity"))
      return;
   if(!CAp::Assert(k>0,__FUNCTION__": K<=0"))
      return;
   if(!CAp::Assert(k<=npoints,__FUNCTION__": K>NPoints"))
      return;
   if(!CAp::Assert(npoints==rep.m_NPoints,__FUNCTION__": NPoints<>Rep.NPoints"))
      return;
//--- Quick exit
   if(npoints==0)
      return;
   if(npoints==1)
     {
      int temp[]={0};
      cz=temp;
      cidx=temp;
      return;
     }
//--- Replay merges, from top to bottom,
//--- keep track of clusters being present at the moment
   ArrayResize(presentclusters,2*npoints-1);
   tmpidx.Resize(npoints);
   ArrayInitialize(presentclusters,false);
   presentclusters[2*npoints-2]=true;
   tmpidx.Fill(2*npoints-2);
   for(int mergeidx=npoints-2; mergeidx>=npoints-k; mergeidx--)
     {
      //--- Update information about clusters being present at the moment
      presentclusters[npoints+mergeidx]=false;
      presentclusters[rep.m_z.Get(mergeidx,0)]=true;
      presentclusters[rep.m_z.Get(mergeidx,1)]=true;
      //--- Update TmpIdx according to the current state of the dataset
      //--- NOTE: TmpIdx contains cluster indexes from [0..2*NPoints-2];
      //---       we will convert them to [0..K-1] later.
      i0=rep.m_pm.Get(mergeidx,0);
      i1=rep.m_pm.Get(mergeidx,1);
      t=rep.m_z.Get(mergeidx,0);
      tmpidx.Fill(t,i0,i1-i0+1);
      i0=rep.m_pm.Get(mergeidx,2);
      i1=rep.m_pm.Get(mergeidx,3);
      t=rep.m_z.Get(mergeidx,1);
      tmpidx.Fill(t,i0,i1-i0+1);
     }
//--- Fill CZ - array which allows us to convert cluster indexes
//--- from one system to another.
   cz.Resize(k);
   clusterindexes.Resize(2*npoints-1);
   t=0;
   for(int i=0; i<2*npoints-1; i++)
     {
      if(presentclusters[i])
        {
         cz.Set(t,i);
         clusterindexes.Set(i,t);
         t++;
        }
     }
   if(!CAp::Assert(t==k,__FUNCTION__": internal error"))
      return;
//--- Convert indexes stored in CIdx
   cidx.Resize(npoints);
   for(int i=0; i<npoints; i++)
      cidx.Set(i,clusterindexes[tmpidx[rep.m_p[i]]]);
  }
//+------------------------------------------------------------------+
//| This function accepts AHC report Rep, desired minimum            |
//| intercluster distance and returns top clusters from hierarchical |
//| clusterization tree which are separated by distance R or HIGHER. |
//| It returns assignment of points to clusters (array of cluster    |
//| indexes).                                                        |
//| There is one more function with similar name -                   |
//| ClusterizerSeparatedByCorr, which returns clusters with          |
//| intercluster correlation equal to R or LOWER (note: higher for   |
//| distance, lower for correlation).                                |
//| INPUT PARAMETERS:                                                |
//|   Rep      -  report from ClusterizerRunAHC() performed on XY    |
//|   R        -   desired minimum intercluster distance, R >= 0     |
//| OUTPUT PARAMETERS:                                               |
//|   K        -  number of clusters, 1 <= K <= NPoints              |
//|   CIdx     -  array[NPoints], I-th element contains cluster      |
//|               index (from 0 to K-1) for I-th point of the dataset|
//|   CZ       -  array[K]. This array allows to convert cluster     |
//|               indexes returned by this function to indexes used  |
//|               by Rep.Z. J-th cluster returned by this function   |
//|               corresponds to CZ[J]-th cluster stored in          |
//|               Rep.Z/PZ/PM. It is guaranteed that CZ[I] < CZ[I+1].|
//| NOTE: K clusters built by this subroutine are assumed to have no |
//|       hierarchy. Although they were obtained by manipulation with|
//|       top K nodes of dendrogram (i.e. hierarchical decomposition |
//|       of dataset), this function does not return information     |
//|       about hierarchy. Each of the clusters stand on its own.    |
//| NOTE: Cluster indexes returned by this function does not         |
//|       correspond to indexes returned in Rep.Z/PZ/PM. Either you  |
//|       work with hierarchical representation of the dataset       |
//|       (dendrogram), or you work with "flat" representation       |
//|       returned by this function. Each of representations has its |
//|       own clusters indexing system (former uses [0,2*NPoints-2]),|
//|       while latter uses [0..K-1]), although it is possible to    |
//|       perform conversion from one system to another by means of  |
//|       CZ array, returned by this function, which allows you to   |
//|       convert indexes stored in CIdx to the numeration system    |
//|       used by Rep.Z.                                             |
//| NOTE: this subroutine is optimized for moderate values of K. Say,|
//|       for K=5 it will perform many times faster than for K=100.  |
//|       Its worst - case performance is O(N*K), although in average|
//|       case it perform better (up to O(N*log(K))).                |
//+------------------------------------------------------------------+
void CClustering::ClusterizerSeparatedByDist(CAHCReport &rep,
                                             double r,
                                             int &k,
                                             CRowInt &cidx,
                                             CRowInt &cz)
  {
//--- initialization
   k=0;
   cidx.Resize(0);
   cz.Resize(0);
//--- check
   if(!CAp::Assert(MathIsValidNumber(r) && r>=0.0,__FUNCTION__": R is infinite or less than 0"))
      return;

   k=1;
   while(k<rep.m_NPoints && rep.m_mergedist[rep.m_NPoints-1-k]>=r)
      k++;
   ClusterizerGetKClusters(rep,k,cidx,cz);
  }
//+------------------------------------------------------------------+
//| This function accepts AHC report Rep, desired maximum            |
//| intercluster correlation and returns top clusters from           |
//| hierarchical clusterization tree which are separated by          |
//| correlation R or LOWER.                                          |
//| It returns assignment of points to clusters(array of cluster     |
//| indexes).                                                        |
//| There is one more function with similar name -                   |
//| ClusterizerSeparatedByDist, which returns clusters with          |
//| intercluster distance equal to R or HIGHER (note: higher for     |
//| distance, lower for correlation).                                |
//| INPUT PARAMETERS:                                                |
//|   Rep      -  report from ClusterizerRunAHC() performed on XY    |
//|   R        -  desired maximum intercluster correlation, -1<=R<=+1|
//| OUTPUT PARAMETERS:                                               |
//|   K        -  number of clusters, 1 <= K <= NPoints              |
//|   CIdx     -  array[NPoints], I-th element contains cluster index|
//|               (from 0 to K-1) for I-th point of the dataset.     |
//|   CZ       -  array[K]. This array allows to convert cluster     |
//|               indexes returned by this function to indexes used  |
//|               by Rep.Z. J-th cluster returned by this function   |
//|               corresponds to CZ[J]-th cluster stored in          |
//|               Rep.Z/PZ/PM. It is guaranteed that CZ[I] < CZ[I+1].|
//| NOTE: K clusters built by this subroutine are assumed to have no |
//|       hierarchy. Although they were obtained by manipulation with|
//|       top K nodes of dendrogram (i.e. hierarchical decomposition |
//|       of dataset), this function does not return information     |
//|       about hierarchy. Each of the clusters stand on its own.    |
//| NOTE: Cluster indexes returned by this function does not         |
//|       correspond to indexes returned in Rep.Z/PZ/PM. Either you  |
//|       work with hierarchical representation of the dataset       |
//|       (dendrogram), or you work with "flat" representation       |
//|       returned by this function. Each of representations has its |
//|       own clusters indexing system (former uses [0,2*NPoints-2]),|
//|       while latter uses [0..K-1]), although it is possible to    |
//|       perform conversion from one system to another by means of  |
//|       CZ array, returned by this function, which allows you to   |
//|       convert indexes stored in CIdx to the numeration system    |
//|       used by Rep.Z.                                             |
//| NOTE: this subroutine is optimized for moderate values of K. Say,|
//|       for K=5 it will perform many times faster than for K=100.  |
//|       Its worst - case performance is O(N*K), although in average|
//|       case it perform better (up to O(N*log(K))).                |
//+------------------------------------------------------------------+
void CClustering::ClusterizerSeparatedByCorr(CAHCReport &rep,
                                             double r,
                                             int &k,
                                             CRowInt &cidx,
                                             CRowInt &cz)
  {
   k=0;
   cidx.Resize(0);
   cz.Resize(0);
   if(!CAp::Assert(MathIsValidNumber(r) && r>=-1.0 && r<=1.0,__FUNCTION__": R is infinite or less than 0"))
      return;
   k=1;
   while(k<rep.m_NPoints && rep.m_mergedist[rep.m_NPoints-1-k]>=(1.0-r))
      k++;
   ClusterizerGetKClusters(rep,k,cidx,cz);
  }
//+------------------------------------------------------------------+
//| K-means++ clusterization                                         |
//| INPUT PARAMETERS:                                                |
//|   XY       -  dataset, array [0..NPoints-1, 0..NVars-1].         |
//|   NPoints  -  dataset size, NPoints >= K                         |
//|   NVars    -  number of variables, NVars >= 1                    |
//|   K        -  desired number of clusters, K >= 1                 |
//|   InitAlgo -  initialization algorithm:                          |
//|               * 0 - automatic selection of best algorithm        |
//|               * 1 - random selection of centers                  |
//|               * 2 - k-means++                                    |
//|               * 3 - fast-greedy init                             |
//|               * -1 - first K rows of dataset are used (special   |
//|                     debug algorithm)                             |
//|   Seed     -  seed value for internal RNG :                      |
//|               * positive value is used to initialize RNG in order|
//|                 to induce deterministic behavior of algorithm    |
//|               * zero or negative value means that random seed is |
//|                 generated                                        |
//|   MaxIts   -  iterations limit or zero for no limit              |
//|   Restarts -  number of restarts, Restarts >= 1                  |
//|   KMeansDbgNoIts -  debug flag; if set, Lloyd's iteration is not |
//|               performed, only initialization phase.              |
//|   Buf      -  special reusable structure which stores previously |
//|               allocated memory, intended to avoid memory         |
//|               fragmentation when solving multiple subsequent     |
//|               problems:                                          |
//|               * MUST BE INITIALIZED WITH KMeansInitBuffers()     |
//|                 CALL BEFORE FIRST PASS TO THIS FUNCTION!         |
//|               * subsequent passes must be made without           |
//|                 re-initialization                                |
//| OUTPUT PARAMETERS:                                               |
//|   Info     -  return code:                                       |
//|               * -3, if task is degenerate(number of distinct     |
//|                     points is less than K)                       |
//|               * -1, if incorrect NPoints/NFeatures/K/Restarts was|
//|                     passed                                       |
//|               *  1, if subroutine finished successfully          |
//|   IterationsCount - actual number of iterations performed by     |
//|               clusterizer                                        |
//|   CCol     -  array[0..NVars-1, 0..K-1]. matrix whose columns    |
//|               store cluster's centers                            |
//|   NeedCCol -  True in case caller requires to store result in    |
//|               CCol                                               |
//|   CRow     -  array[0..K-1, 0..NVars-1], same as CCol, but       |
//|               centers are stored in rows                         |
//|   NeedCRow -  True in case caller requires to store result in    |
//|               CCol                                               |
//|   XYC      -  array[NPoints], which contains cluster indexes     |
//|   Energy   -  merit function of clusterization                   |
//+------------------------------------------------------------------+
void CClustering::KMeansGenerateInternal(CMatrixDouble &xy,
                                         int npoints,
                                         int nvars,
                                         int k,
                                         int initalgo,
                                         int seed,
                                         int maxits,
                                         int restarts,
                                         bool kmeansdbgnoits,
                                         int &info,
                                         int &iterationscount,
                                         CMatrixDouble &ccol,
                                         bool needccol,
                                         CMatrixDouble &crow,
                                         bool needcrow,
                                         CRowInt &xyc,
                                         double &energy,
                                         CKmeansBuffers &buf)
  {
//--- create variables
   int    i1=0;
   double e=0;
   double eprev=0;
   double v=0;
   double vv=0;
   bool   waschanges;
   bool   zerosizeclusters;
   int    pass=0;
   int    itcnt=0;
   CHighQualityRandState rs;

   info=0;
   iterationscount=0;
   ccol.Resize(0,0);
   crow.Resize(0,0);
   xyc.Resize(0);
   energy=0;
//--- Test parameters
   if(npoints<k || nvars<1 || k<1 || restarts<1)
     {
      info=-1;
      iterationscount=0;
      return;
     }
//--- TODO: special case K=1
//--- TODO: special case K=NPoints
   info=1;
   iterationscount=0;
//--- Multiple passes of k-means++ algorithm
   if(seed<=0)
      CHighQualityRand::HQRndRandomize(rs);
   else
      CHighQualityRand::HQRndSeed(325355,seed,rs);
   xyc.Resize(npoints);
   CApServ::RMatrixSetLengthAtLeast(buf.m_ctbest,k,nvars);
   CApServ::IVectorSetLengthAtLeast(buf.m_xycbest,npoints);
   buf.m_d2.Resize(npoints);
   CApServ::IVectorSetLengthAtLeast(buf.m_csizes,k);
   energy=CMath::m_maxrealnumber;
   for(pass=1; pass<=restarts; pass++)
     {
      //--- Select initial centers.
      //--- Note that for performance reasons centers are stored in ROWS of CT, not
      //--- in columns. We'll transpose CT in the end and store it in the C.
      //--- Also note that SelectInitialCenters() may return degenerate set of centers
      //--- (some of them have no corresponding points in dataset, some are non-distinct).
      //--- Algorithm below is robust enough to deal with such set.
      SelectInitialCenters(xy,npoints,nvars,initalgo,rs,k,buf.m_ct,buf.m_initbuf);
      //--- Lloyd's iteration
      if(!kmeansdbgnoits)
        {
         //--- Perform iteration as usual, in normal mode
         xyc.Fill(-1,0,npoints);
         eprev=CMath::m_maxrealnumber;
         e=CMath::m_maxrealnumber;
         itcnt=0;
         while(maxits==0 || itcnt<maxits)
           {
            //--- Update iteration counter
            itcnt++;
            iterationscount++;
            //--- Call KMeansUpdateDistances(), fill XYC with center numbers,
            //--- D2 with center distances.
            buf.m_xycprev=xyc;
            buf.m_xycprev.Resize(npoints);
            KMeansUpdateDistances(xy,0,npoints,nvars,buf.m_ct,0,k,xyc,buf.m_d2);
            waschanges=false;
            for(int i=0; i<npoints; i++)
               waschanges=waschanges || xyc[i]!=buf.m_xycprev[i];
            //--- Update centers
            buf.m_csizes.Fill(0,0,k);
            buf.m_ct=matrix<double>::Zeros(k,nvars);
            for(int i=0; i<npoints; i++)
              {
               buf.m_csizes.Add(xyc[i],1);
               for(int i_=0; i_<nvars; i_++)
                 {
                  buf.m_ct.Add(xyc[i],i_,xy.Get(i,i_));
                 }
              }
            zerosizeclusters=false;
            for(int j=0; j<k; j++)
              {
               if(buf.m_csizes[j]!=0)
                 {
                  v=(double)1/(double)buf.m_csizes[j];
                  buf.m_ct.Row(j,buf.m_ct.Row(j)*v);
                 }
               zerosizeclusters=zerosizeclusters || buf.m_csizes[j]==0;
              }
            if(zerosizeclusters)
              {
               //--- Some clusters have zero size - rare, but possible.
               //--- We'll choose new centers for such clusters using k-means++ rule
               //--- and restart algorithm, decrementing iteration counter
               //--- in order to allow one more iteration (this one was useless
               //--- and should not be counted).
               if(!FixCenters(xy,npoints,nvars,buf.m_ct,k,buf.m_initbuf))
                 {
                  info=-3;
                  return;
                 }
               itcnt--;
               continue;
              }
            //--- Stop if one of two conditions is met:
            //--- 1. nothing has changed during iteration
            //--- 2. energy function increased after recalculation on new centers
            e=0;
            for(int i=0; i<npoints; i++)
              {
               v=0.0;
               i1=xyc[i];
               for(int j=0; j<nvars; j++)
                 {
                  vv=xy.Get(i,j)-buf.m_ct.Get(i1,j);
                  v+=vv*vv;
                 }
               e+=v;
              }
            if(!waschanges || e>=eprev)
               break;
            //--- Update EPrev
            eprev=e;
           }
        }
      else
        {
         //--- Debug mode: no Lloyd's iteration.
         //--- We just calculate potential E.
         KMeansUpdateDistances(xy,0,npoints,nvars,buf.m_ct,0,k,xyc,buf.m_d2);
         e=buf.m_d2.Sum();
        }
      //--- Compare E with best centers found so far
      if(e<energy)
        {
         //--- store partition.
         energy=e;
         CBlas::CopyMatrix(buf.m_ct,0,k-1,0,nvars-1,buf.m_ctbest,0,k-1,0,nvars-1);
         buf.m_xycbest=xyc;
        }
     }
//--- Copy and transpose
   if(needccol)
     {
      ccol.Resize(nvars,k);
      CBlas::CopyAndTranspose(buf.m_ctbest,0,k-1,0,nvars-1,ccol,0,nvars-1,0,k-1);
     }
   if(needcrow)
     {
      crow.Resize(k,nvars);
      CAblas::RMatrixCopy(k,nvars,buf.m_ctbest,0,0,crow,0,0);
     }
   xyc=buf.m_xycbest;
  }
//+------------------------------------------------------------------+
//| This procedure recalculates distances from points to centers and |
//| assigns each point to closest center.                            |
//| INPUT PARAMETERS:                                                |
//|   XY       -  dataset, array [0..NPoints-1, 0..NVars-1].         |
//|   Idx0, Idx1 -   define range of dataset [Idx0, Idx1) to process;|
//|               right boundary is not included.                    |
//|   NVars    -  number of variables, NVars >= 1                    |
//|   CT       -  matrix of centers, centers are stored in rows      |
//|   CIdx0, CIdx1 - define range of centers [CIdx0, CIdx1) to       |
//|               process; right boundary is not included.           |
//|   XYC      -  preallocated output buffer,                        |
//|   XYDist2  -  preallocated output buffer                         |
//|   Tmp      -  temporary buffer, automatically reallocated if     |
//|               needed                                             |
//| OUTPUT PARAMETERS:                                               |
//|   XYC      -  new assignment of points to centers are stored     |
//|               in [Idx0, Idx1)                                    |
//|   XYDist2  -  squared distances from points to their centers are |
//|               stored in [Idx0, Idx1)                             |
//+------------------------------------------------------------------+
void CClustering::KMeansUpdateDistances(CMatrixDouble &xy,
                                        int idx0,int idx1,int nvars,
                                        CMatrixDouble &ct,int cidx0,
                                        int cidx1,CRowInt &xyc,
                                        CRowDouble &xydist2)
  {
//--- create variables
   int    i0=0;
   int    i1=0;
   int    cclosest=0;
   double dclosest=0;
   double vv=0;
   CApBuff buf;
   double rcomplexity=0;
   int    task0=0;
   int    task1=0;
   int    pblkcnt=0;
   int    cblkcnt=0;
   int    vblkcnt=0;
   int    p0=0;
   int    p1=0;
   int    c0=0;
   int    c1=0;
   int    v0=0;
   int    v1=0;
   double v00=0;
   double v01=0;
   double v10=0;
   double v11=0;
   double vp0=0;
   double vp1=0;
   double vc0=0;
   double vc1=0;
   int    pcnt=0;
   int    pcntpadded=0;
   int    ccnt=0;
   int    ccntpadded=0;
   int    offs0=0;
   int    offs00=0;
   int    offs01=0;
   int    offs10=0;
   int    offs11=0;
   int    vcnt=0;
   int    stride=0;
//--- Quick exit for special cases
   if(idx1<=idx0)
      return;
   if(cidx1<=cidx0)
      return;
   if(nvars<=0)
      return;
//--- Dataset chunk is selected.
//--- Process it with blocked algorithm:
//--- * iterate over points, process them in KMeansBlockSize-ed chunks
//--- * for each chunk of dataset, iterate over centers, process them in KMeansBlockSize-ed chunks
//--- * for each chunk of dataset/centerset, iterate over variables, process them in KMeansBlockSize-ed chunks
   if(!CAp::Assert(m_kmeansblocksize%2==0,__FUNCTION__": internal error"))
      return;
   CApServ::RVectorSetLengthAtLeast(buf.m_ra0,m_kmeansblocksize*m_kmeansblocksize);
   CApServ::RVectorSetLengthAtLeast(buf.m_ra1,m_kmeansblocksize*m_kmeansblocksize);
   CApServ::RVectorSetLengthAtLeast(buf.m_ra2,m_kmeansblocksize*m_kmeansblocksize);
   CApServ::RVectorSetLengthAtLeast(buf.m_ra3,m_kmeansblocksize);
   CApServ::IVectorSetLengthAtLeast(buf.m_ia3,m_kmeansblocksize);
   pblkcnt=CApServ::ChunksCount(idx1-idx0,m_kmeansblocksize);
   cblkcnt=CApServ::ChunksCount(cidx1-cidx0,m_kmeansblocksize);
   vblkcnt=CApServ::ChunksCount(nvars,m_kmeansblocksize);
   for(int pblk=0; pblk<pblkcnt; pblk++)
     {
      //--- Process PBlk-th chunk of dataset.
      p0=idx0+pblk*m_kmeansblocksize;
      p1=MathMin(p0+m_kmeansblocksize,idx1);
      //--- Prepare RA3[]/IA3[] for storage of best distances and best cluster numbers.
      buf.m_ra3=vector<double>::Full(m_kmeansblocksize,CMath::m_maxrealnumber);
      buf.m_ia3.Fill(-1,0,m_kmeansblocksize);
      //--- Iterare over chunks of centerset.
      for(int cblk=0; cblk<cblkcnt; cblk++)
        {
         //--- Process CBlk-th chunk of centerset
         c0=cidx0+cblk*m_kmeansblocksize;
         c1=MathMin(c0+m_kmeansblocksize,cidx1);
         //--- At this point we have to calculate a set of pairwise distances
         //--- between points [P0,P1) and centers [C0,C1) and select best center
         //--- for each point. It can also be done with blocked algorithm
         //--- (blocking for variables).
         //--- Following arrays are used:
         //--- * RA0[] - matrix of distances, padded by zeros for even size,
         //---           rows are stored with stride KMeansBlockSize.
         //--- * RA1[] - matrix of points (variables corresponding to current
         //---           block are extracted), padded by zeros for even size,
         //---           rows are stored with stride KMeansBlockSize.
         //--- * RA2[] - matrix of centers (variables corresponding to current
         //---           block are extracted), padded by zeros for even size,
         //---           rows are stored with stride KMeansBlockSize.
         pcnt=p1-p0;
         pcntpadded=pcnt+pcnt%2;
         ccnt=c1-c0;
         ccntpadded=ccnt+ccnt%2;
         stride=m_kmeansblocksize;
         if(!CAp::Assert(pcntpadded<=m_kmeansblocksize,__FUNCTION__": integrity error"))
            return;
         if(!CAp::Assert(ccntpadded<=m_kmeansblocksize,__FUNCTION__": integrity error"))
            return;
         for(int i=0; i<pcntpadded; i++)
            for(int j=0; j<ccntpadded; j++)
               buf.m_ra0.Set(i*stride+j,0.0);
         for(int vblk=0; vblk<vblkcnt; vblk++)
           {
            //--- Fetch VBlk-th block of variables to arrays RA1 (points) and RA2 (centers).
            //--- Pad points and centers with zeros.
            v0=vblk*m_kmeansblocksize;
            v1=MathMin(v0+m_kmeansblocksize,nvars);
            vcnt=v1-v0;
            for(int i=0; i<pcnt; i++)
               for(int j=0; j<vcnt; j++)
                  buf.m_ra1.Set(i*stride+j,xy.Get(p0+i,v0+j));
            for(int i=pcnt; i<pcntpadded; i++)
               for(int j=0; j<vcnt; j++)
                  buf.m_ra1.Set(i*stride+j,0.0);
            for(int i=0; i<ccnt; i++)
               for(int j=0; j<vcnt; j++)
                  buf.m_ra2.Set(i*stride+j,ct.Get(c0+i,v0+j));
            for(int i=ccnt; i<ccntpadded; i++)
               for(int j=0; j<vcnt; j++)
                  buf.m_ra2.Set(i*stride+j,0.0);
            //--- Update distance matrix with sums-of-squared-differences of RA1 and RA2
            i0=0;
            while(i0<pcntpadded)
              {
               i1=0;
               while(i1<ccntpadded)
                 {
                  offs0=i0*stride+i1;
                  v00=buf.m_ra0[offs0];
                  v01=buf.m_ra0[offs0+1];
                  v10=buf.m_ra0[offs0+stride];
                  v11=buf.m_ra0[offs0+stride+1];
                  offs00=i0*stride;
                  offs01=offs00+stride;
                  offs10=i1*stride;
                  offs11=offs10+stride;
                  for(int j=0; j<vcnt; j++)
                    {
                     vp0=buf.m_ra1[offs00+j];
                     vp1=buf.m_ra1[offs01+j];
                     vc0=buf.m_ra2[offs10+j];
                     vc1=buf.m_ra2[offs11+j];
                     vv=vp0-vc0;
                     v00+=vv*vv;
                     vv=vp0-vc1;
                     v01+=vv*vv;
                     vv=vp1-vc0;
                     v10+=vv*vv;
                     vv=vp1-vc1;
                     v11+=vv*vv;
                    }
                  offs0=i0*stride+i1;
                  buf.m_ra0.Set(offs0,v00);
                  buf.m_ra0.Set(offs0+1,v01);
                  buf.m_ra0.Set(offs0+stride,v10);
                  buf.m_ra0.Set(offs0+stride+1,v11);
                  i1+=2;
                 }
               i0+=2;
              }
           }
         for(int i=0; i<pcnt; i++)
           {
            cclosest=buf.m_ia3[i];
            dclosest=buf.m_ra3[i];
            for(int j=0; j<ccnt; j++)
               if(buf.m_ra0[i*stride+j]<dclosest)
                 {
                  dclosest=buf.m_ra0[i*stride+j];
                  cclosest=c0+j;
                 }
            buf.m_ia3.Set(i,cclosest);
            buf.m_ra3.Set(i,dclosest);
           }
        }
      //--- Store best centers to XYC[]
      for(int i=p0; i<p1; i++)
        {
         xyc.Set(i,buf.m_ia3[i-p0]);
         xydist2.Set(i,buf.m_ra3[i-p0]);
        }
     }
  }
//+------------------------------------------------------------------+
//| This function selects initial centers according to specified     |
//| initialization algorithm.                                        |
//| IMPORTANT: this function provides no guarantees regarding        |
//|            selection of DIFFERENT centers. Centers returned by   |
//|            this function may include duplicates(say, when random |
//|            sampling is used). It is also possible that some      |
//|            centers are empty. Algorithm which uses this function |
//|            must be able to deal with it. Say, you may want to use|
//|            FixCenters() in order to fix empty centers.           |
//| INPUT PARAMETERS:                                                |
//|   XY       -  dataset, array [0..NPoints-1, 0..NVars-1].         |
//|   NPoints  -  points count                                       |
//|   NVars    -  number of variables, NVars >= 1                    |
//|   InitAlgo -  initialization algorithm:                          |
//|               * 0 - automatic selection of best algorithm        |
//|               * 1 - random selection                             |
//|               * 2 - k-means++                                    |
//|               * 3 - fast-greedy init                             |
//|               * -1 - first K rows of dataset are used (debug     |
//|                     algorithm)                                   |
//|   RS       -  RNG used to select centers                         |
//|   K        -  number of centers, K >= 1                          |
//|   CT       -  possibly preallocated output buffer, resized if    |
//|               needed                                             |
//|   InitBuf  -  internal buffer, possibly unitialized instance of  |
//|               APBuffers. It is recommended to use this instance  |
//|               only with SelectInitialCenters() and FixCenters()  |
//|               functions, because these functions may allocate    |
//|               really large storage.                              |
//| OUTPUT PARAMETERS:                                               |
//|   CT       -  set of K clusters, one per row                     |
//| RESULT: True on success, False on failure(impossible to create K |
//|         independent clusters)                                    |
//+------------------------------------------------------------------+
void CClustering::SelectInitialCenters(CMatrixDouble &xy,int npoints,
                                       int nvars,int initalgo,CHighQualityRandState &rs,
                                       int k,CMatrixDouble &ct,CApBuff &initbuf)
  {
//--- create variables
   int    cidx=0;
   int    i=0;
   int    j=0;
   double v=0;
   double vv=0;
   double s=0;
   int    lastnz=0;
   int    ptidx=0;
   int    samplesize=0;
   int    samplescntnew=0;
   int    samplescntall=0;
   double samplescale=0;
   int    i_=0;
//--- Check parameters
   if(!CAp::Assert(npoints>0,__FUNCTION__": internal error"))
      return;
   if(!CAp::Assert(nvars>0,__FUNCTION__": internal error"))
      return;
   if(!CAp::Assert(k>0,__FUNCTION__": internal error"))
      return;
   if(initalgo==0)
      initalgo=3;
   CApServ::RMatrixSetLengthAtLeast(ct,k,nvars);
//--- Random initialization
   if(initalgo==-1)
     {
      for(i=0; i<k; i++)
         ct.Row(i,xy[i%npoints]+0);
      return;
     }
//--- Random initialization
   if(initalgo==1)
     {
      for(i=0; i<k; i++)
        {
         j=CHighQualityRand::HQRndUniformI(rs,npoints);
         ct.Row(i,xy[j]+0);
        }
      return;
     }
//--- k-means++ initialization
   if(initalgo==2)
     {
      //--- Prepare distances array.
      //--- Select initial center at random.
      initbuf.m_ra0=vector<double>::Full(npoints,CMath::m_maxrealnumber);
      ptidx=CHighQualityRand::HQRndUniformI(rs,npoints);
      ct.Row(0,xy[ptidx]+0);
      //--- For each newly added center repeat:
      //--- * reevaluate distances from points to best centers
      //--- * sample points with probability dependent on distance
      //--- * add new center
      for(cidx=0; cidx<k-1; cidx++)
        {
         //--- Reevaluate distances
         s=0.0;
         for(i=0; i<npoints; i++)
           {
            v=0.0;
            for(j=0; j<=nvars-1; j++)
              {
               vv=xy.Get(i,j)-ct.Get(cidx,j);
               v+=vv*vv;
              }
            if(v<initbuf.m_ra0[i])
               initbuf.m_ra0.Set(i,v);
            s+=initbuf.m_ra0[i];
           }
         //
         //--- If all distances are zero, it means that we can not find enough
         //--- distinct points. In this case we just select non-distinct center
         //--- at random and continue iterations. This issue will be handled
         //--- later in the FixCenters() function.
         //
         if(s==0.0)
           {
            ptidx=CHighQualityRand::HQRndUniformI(rs,npoints);
            ct.Row(cidx+1,xy[ptidx]+0);
            continue;
           }
         //--- Select point as center using its distance.
         //--- We also handle situation when because of rounding errors
         //--- no point was selected - in this case, last non-zero one
         //--- will be used.
         v=CHighQualityRand::HQRndUniformR(rs);
         vv=0.0;
         lastnz=-1;
         ptidx=-1;
         for(i=0; i<npoints; i++)
           {
            if(initbuf.m_ra0[i]==0.0)
               continue;
            lastnz=i;
            vv+=initbuf.m_ra0[i];
            if(v<=vv/s)
              {
               ptidx=i;
               break;
              }
           }
         if(!CAp::Assert(lastnz>=0,__FUNCTION__": integrity error"))
            return;
         if(ptidx<0)
            ptidx=lastnz;
         ct.Row(cidx+1,xy[ptidx]+0);
        }
      return;
     }
//--- "Fast-greedy" algorithm based on "Scalable k-means++".
//--- We perform several rounds, within each round we sample about 0.5*K points
//--- (not exactly 0.5*K) until we have 2*K points sampled. Before each round
//--- we calculate distances from dataset points to closest points sampled so far.
//--- We sample dataset points independently using distance xtimes 0.5*K divided by total
//--- as probability (similar to k-means++, but each point is sampled independently;
//--- after each round we have roughtly 0.5*K points added to sample).
//--- After sampling is done, we run "greedy" version of k-means++ on this subsample
//--- which selects most distant point on every round.
   if(initalgo==3)
     {
      //--- Prepare arrays.
      //--- Select initial center at random, add it to "new" part of sample,
      //--- which is stored at the beginning of the array
      samplesize=2*k;
      samplescale=0.5*k;
      CApServ::RMatrixSetLengthAtLeast(initbuf.m_rm0,samplesize,nvars);
      ptidx=CHighQualityRand::HQRndUniformI(rs,npoints);
      initbuf.m_rm0.Row(0,xy[ptidx]+0);
      samplescntnew=1;
      samplescntall=1;
      initbuf.m_ra1=vector<double>::Zeros(npoints);
      CApServ::IVectorSetLengthAtLeast(initbuf.m_ia1,npoints);
      initbuf.m_ra0=vector<double>::Full(npoints,CMath::m_maxrealnumber);
      //--- Repeat until samples count is 2*K
      while(samplescntall<samplesize)
        {
         //--- Evaluate distances from points to NEW centers, store to RA1.
         //--- Reset counter of "new" centers.
         KMeansUpdateDistances(xy,0,npoints,nvars,initbuf.m_rm0,samplescntall-samplescntnew,samplescntall,initbuf.m_ia1,initbuf.m_ra1);
         samplescntnew=0;
         //--- Merge new distances with old ones.
         //--- Calculate sum of distances, if sum is exactly zero - fill sample
         //--- by randomly selected points and terminate.
         s=0.0;
         for(i=0; i<npoints; i++)
           {
            initbuf.m_ra0.Set(i,MathMin(initbuf.m_ra0[i],initbuf.m_ra1[i]));
            s+=initbuf.m_ra0[i];
           }
         if(s==0.0)
           {
            while(samplescntall<samplesize)
              {
               ptidx=CHighQualityRand::HQRndUniformI(rs,npoints);
               initbuf.m_rm0.Row(samplescntall,xy[ptidx]+0);
               samplescntall++;
               samplescntnew++;
              }
            break;
           }
         //--- Sample points independently.
         for(i=0; i<npoints; i++)
           {
            if(samplescntall==samplesize)
               break;
            if(initbuf.m_ra0[i]==0.0)
               continue;
            if(CHighQualityRand::HQRndUniformR(rs)<=(samplescale*initbuf.m_ra0[i]/s))
              {
               initbuf.m_rm0.Row(samplescntall,xy[i]+0);
               samplescntall++;
               samplescntnew++;
              }
           }
        }
      //--- Run greedy version of k-means on sampled points
 
      initbuf.m_ra0=vector<double>::Full(samplescntall,CMath::m_maxrealnumber);
      ptidx=CHighQualityRand::HQRndUniformI(rs,samplescntall);
      ct.Row(0,initbuf.m_rm0[ptidx]+0);
      for(cidx=0; cidx<k-1; cidx++)
        {
         //--- Reevaluate distances
         for(i=0; i<samplescntall; i++)
           {
            v=0.0;
            for(j=0; j<nvars; j++)
              {
               vv=initbuf.m_rm0.Get(i,j)-ct.Get(cidx,j);
               v+=vv*vv;
              }
            if(v<initbuf.m_ra0[i])
               initbuf.m_ra0.Set(i,v);
           }
         //--- Select point as center in greedy manner - most distant
         //--- point is selected.
         ptidx=0;
         for(i=0; i<samplescntall; i++)
           {
            if(initbuf.m_ra0[i]>initbuf.m_ra0[ptidx])
               ptidx=i;
           }
         ct.Row(cidx+1,initbuf.m_rm0[ptidx]+0);
        }
      return;
     }
//--- Internal error
   CAp::Assert(false,__FUNCTION__": internal error");
  }
//+------------------------------------------------------------------+
//| This function "fixes" centers, i.e. replaces ones which have  no |
//| neighbor points by new centers which have at least one neighbor. |
//| If it is impossible to fix centers(not enough distinct points in |
//| the dataset), this function returns False.                       |
//| INPUT PARAMETERS:                                                |
//|   XY       -  dataset, array [0..NPoints-1, 0..NVars-1].         |
//|   NPoints  -  points count, >= 1                                 |
//|   NVars    -  number of variables, NVars >= 1                    |
//|   CT       -  centers                                            |
//|   K        -  number of centers, K >= 1                          |
//|   InitBuf  -  internal buffer, possibly unitialized instance of  |
//|               APBuffers. It is recommended to use this instance  |
//|               only with SelectInitialCenters() and FixCenters()  |
//|               functions, because these functions may allocate    |
//|               really large storage.                              |
//|   UpdatePool - shared pool seeded with instance of APBuffers     |
//|               structure (seed instance can be unitialized). Used |
//|               internally with KMeansUpdateDistances() function.  |
//|               It is recommended to use this pool ONLY with       |
//|               KMeansUpdateDistances() function.                  |
//| OUTPUT PARAMETERS:                                               |
//|   CT       -  set of K centers, one per row                      |
//| RESULT: True on success, False on failure(impossible to create K |
//|         independent clusters)                                    |
//+------------------------------------------------------------------+
bool CClustering::FixCenters(CMatrixDouble &xy,
                             int npoints,
                             int nvars,
                             CMatrixDouble &ct,
                             int k,
                             CApBuff &initbuf)
  {
//--- create variables
   int    fixiteration=0;
   int    centertofix=0;
   int    pdistant=0;
   double ddistant=0;
   double v=0;
//--- check
   if(!CAp::Assert(npoints>=1,__FUNCTION__": internal error"))
      return(false);
   if(!CAp::Assert(nvars>=1,__FUNCTION__": internal error"))
      return(false);
   if(!CAp::Assert(k>=1,__FUNCTION__": internal error"))
      return(false);
//--- Calculate distances from points to best centers (RA0)
//--- and best center indexes (IA0)
   CApServ::IVectorSetLengthAtLeast(initbuf.m_ia0,npoints);
   CApServ::RVectorSetLengthAtLeast(initbuf.m_ra0,npoints);
   KMeansUpdateDistances(xy,0,npoints,nvars,ct,0,k,initbuf.m_ia0,initbuf.m_ra0);
//--- Repeat loop:
//--- * find first center which has no corresponding point
//--- * set it to the most distant (from the rest of the centerset) point
//--- * recalculate distances, update IA0/RA0
//--- * repeat
//--- Loop is repeated for at most 2*K iterations. It is stopped once we have
//--- no "empty" clusters.
   CApServ::BVectorSetLengthAtLeast(initbuf.m_ba,k);
   for(fixiteration=0; fixiteration<=2*k; fixiteration++)
     {
      //--- Select center to fix (one which is not mentioned in IA0),
      //--- terminate if there is no such center.
      //--- BA0[] stores True for centers which have at least one point.
      ArrayFill(initbuf.m_ba,0,k,false);
      for(int i=0; i<npoints; i++)
         initbuf.m_ba[initbuf.m_ia0[i]]=true;
      centertofix=-1;
      for(int i=0; i<k; i++)
         if(!initbuf.m_ba[i])
           {
            centertofix=i;
            break;
           }
      if(centertofix<0)
         return(true);
      //--- Replace center to fix by the most distant point.
      //--- Update IA0/RA0
      pdistant=0;
      ddistant=initbuf.m_ra0[pdistant];
      for(int i=0; i<npoints; i++)
         if(initbuf.m_ra0[i]>ddistant)
           {
            ddistant=initbuf.m_ra0[i];
            pdistant=i;
           }
      if(ddistant==0.0)
         break;
      ct.Row(centertofix,xy[pdistant]+0);
      for(int i=0; i<npoints; i++)
        {
         v=0.0;
         for(int j=0; j<nvars; j++)
            v=v+CMath::Sqr(xy.Get(i,j)-ct.Get(centertofix,j));
         if(v<initbuf.m_ra0[i])
           {
            initbuf.m_ra0.Set(i,v);
            initbuf.m_ia0.Set(i,centertofix);
           }
        }
     }
//--- return result
   return(false);
  }
//+------------------------------------------------------------------+
//| This function  performs  agglomerative  hierarchical  clustering |
//| using precomputed distance matrix. Internal function, should not |
//| be called directly.                                              |
//| INPUT PARAMETERS:                                                |
//|   S        -  clusterizer state, initialized by                  |
//|               ClusterizerCreate()                                |
//|   D        -  distance matrix, array[S.m_nfeatures,S.m_nfeatures]|
//|               Contents  of  the  matrix  is  destroyed  during   |
//|               algorithm operation.                               |
//| OUTPUT PARAMETERS:                                               |
//|   Rep      -  clustering results; see description of AHCReport   |
//|               structure for more information.                    |
//+------------------------------------------------------------------+
void CClustering::ClusterizerRunAHCInternal(CClusterizerState &s,
                                            CMatrixDouble &d,
                                            CAHCReport &rep)
  {
//--- create variables
   int    i=0;
   int    j=0;
   int    k=0;
   double v=0;
   int    mergeidx=0;
   int    c0=0;
   int    c1=0;
   int    s0=0;
   int    s1=0;
   int    ar=0;
   int    br=0;
   CRowInt cidx;
   CRowInt csizes;
   CRowInt nnidx;
   CMatrixInt cinfo;
   int    n0=0;
   int    n1=0;
   int    ni=0;
   double d01=0;
   int    npoints=s.m_NPoints;
//--- Fill Rep.NPoints, quick exit when NPoints<=1
   rep.m_NPoints=npoints;
//--- Quick exit
   if(npoints==0)
     {
      rep.m_p.Resize(0);
      rep.m_z.Resize(0,0);
      rep.m_pz.Resize(0,0);
      rep.m_pm.Resize(0,0);
      rep.m_mergedist.Resize(0);
      rep.m_terminationtype=1;
      return;
     }
   if(npoints==1)
     {
      rep.m_p.Resize(1);
      rep.m_z.Resize(0,0);
      rep.m_pz.Resize(0,0);
      rep.m_pm.Resize(0,0);
      rep.m_mergedist.Resize(0);
      rep.m_p.Set(0,0);
      rep.m_terminationtype=1;
      return;
     }
//--- Allocate
   rep.m_z.Resize(npoints-1,2);
   rep.m_mergedist.Resize(npoints-1);
   rep.m_terminationtype=1;
//--- Build list of nearest neighbors
   nnidx.Resize(npoints);
   for(i=0; i<npoints; i++)
     {
      //--- Calculate index of the nearest neighbor
      k=-1;
      v=CMath::m_maxrealnumber;
      for(j=0; j<npoints; j++)
         if(j!=i && d.Get(i,j)<v)
           {
            k=j;
            v=d.Get(i,j);
           }
      if(!CAp::Assert(v<CMath::m_maxrealnumber,__FUNCTION__": internal error"))
         return;
      nnidx.Set(i,k);
     }
//--- For AHCAlgo=4 (Ward's method) replace distances by their squares times 0.5
   if(s.m_ahcalgo==4)
     {
      d.Resize(npoints,npoints);
      d=MathPow(d.ToMatrix()+0,2.0)*0.5;
     }
//--- Distance matrix is built, perform merges.
//--- NOTE 1: CIdx is array[NPoints] which maps rows/columns of the
//---         distance matrix D to indexes of clusters. Values of CIdx
//---         from [0,NPoints) denote single-point clusters, and values
//---         from [NPoints,2*NPoints-1) denote ones obtained by merging
//---         smaller clusters. Negative calues correspond to absent clusters.
//---         Initially it contains [0...NPoints-1], after each merge
//---         one element of CIdx (one with index C0) is replaced by
//---         NPoints+MergeIdx, and another one with index C1 is
//---         rewritten by -1.
//--- NOTE 2: CSizes is array[NPoints] which stores sizes of clusters.
   cidx.Resize(npoints);
   csizes.Resize(npoints);
   for(i=0; i<npoints; i++)
     {
      cidx.Set(i,i);
      csizes.Set(i,1);
     }
   for(mergeidx=0; mergeidx<npoints-1; mergeidx++)
     {
      //--- Select pair of clusters (C0,C1) with CIdx[C0]<CIdx[C1] to merge.
      c0=-1;
      c1=-1;
      d01=CMath::m_maxrealnumber;
      for(i=0; i<npoints; i++)
         if(cidx[i]>=0)
           {
            if(d.Get(i,nnidx[i])<d01)
              {
               c0=i;
               c1=nnidx[i];
               d01=d.Get(i,nnidx[i]);
              }
           }
      if(!CAp::Assert(d01<CMath::m_maxrealnumber,__FUNCTION__": internal error"))
         return;
      if(cidx[c0]>cidx[c1])
        {
         i=c1;
         c1=c0;
         c0=i;
        }
      //--- Fill one row of Rep.Z and one element of Rep.MergeDist
      rep.m_z.Set(mergeidx,0,cidx[c0]);
      rep.m_z.Set(mergeidx,1,cidx[c1]);
      rep.m_mergedist.Set(mergeidx,d01);
      //--- Update distance matrix:
      //--- * row/column C0 are updated by distances to the new cluster
      //--- * row/column C1 are considered empty (we can fill them by zeros,
      //---   but do not want to spend time - we just ignore them)
      //--- NOTE: it is important to update distance matrix BEFORE CIdx/CSizes
      //---       are updated.
      if(!CAp::Assert(s.m_ahcalgo==0 || s.m_ahcalgo==1 || s.m_ahcalgo==2 || s.m_ahcalgo==3 || s.m_ahcalgo==4,
                      __FUNCTION__": internal error"))
         return;
      for(i=0; i<npoints; i++)
        {
         if(i!=c0 && i!=c1)
           {
            n0=csizes[c0];
            n1=csizes[c1];
            ni=csizes[i];
            switch(s.m_ahcalgo)
              {
               case 0:
                  d.Set(i,c0,MathMax(d.Get(i,c0),d.Get(i,c1)));
                  break;
               case 1:
                  d.Set(i,c0,MathMin(d.Get(i,c0),d.Get(i,c1)));
                  break;
               case 2:
                  d.Set(i,c0,(csizes[c0]*d.Get(i,c0)+csizes[c1]*d.Get(i,c1))/(csizes[c0]+csizes[c1]));
                  break;
               case 3:
                  d.Set(i,c0,(d.Get(i,c0)+d.Get(i,c1))/2);
                  break;
               case 4:
                  d.Set(i,c0,((n0+ni)*d.Get(i,c0)+(n1+ni)*d.Get(i,c1)-ni*d01)/(n0+n1+ni));
                  break;
              }
            d.Set(c0,i,d.Get(i,c0));
           }
        }
      //--- Update CIdx and CSizes
      cidx.Set(c0,npoints+mergeidx);
      cidx.Set(c1,-1);
      csizes.Set(c0,csizes[c0]+csizes[c1]);
      csizes.Set(c1,0);
      //--- Update nearest neighbors array:
      //--- * update nearest neighbors of everything except for C0/C1
      //--- * update neighbors of C0/C1
      for(i=0; i<npoints; i++)
        {
         if(cidx[i]>=0 && i!=c0 && (nnidx[i]==c0 || nnidx[i]==c1))
           {
            //--- I-th cluster which is distinct from C0/C1 has former C0/C1 cluster as its nearest
            //--- neighbor. We handle this issue depending on specific AHC algorithm being used.
            if(s.m_ahcalgo==1)
              {
               //--- Single linkage. Merging of two clusters together
               //--- does NOT change distances between new cluster and
               //--- other clusters.
               //
               //--- The only thing we have to do is to update nearest neighbor index
               nnidx.Set(i,c0);
              }
            else
              {
               //--- Something other than single linkage. We have to re-examine
               //--- all the row to find nearest neighbor.
               k=-1;
               v=CMath::m_maxrealnumber;
               for(j=0; j<npoints; j++)
                 {
                  if(cidx[j]>=0 && j!=i && d.Get(i,j)<v)
                    {
                     k=j;
                     v=d.Get(i,j);
                    }
                 }
               if(!CAp::Assert(v<CMath::m_maxrealnumber || mergeidx==npoints-2,__FUNCTION__": internal error"))
                  return;
               nnidx.Set(i,k);
              }
           }
        }
      k=-1;
      v=CMath::m_maxrealnumber;
      for(j=0; j<npoints; j++)
         if(cidx[j]>=0 && j!=c0 && d.Get(c0,j)<v)
           {
            k=j;
            v=d.Get(c0,j);
           }
      if(!CAp::Assert(v<CMath::m_maxrealnumber || mergeidx==npoints-2,__FUNCTION__": internal error"))
         return;
      nnidx.Set(c0,k);
     }
//--- Calculate Rep.P and Rep.PM.
//--- In order to do that, we fill CInfo matrix - (2*NPoints-1)*3 matrix,
//--- with I-th row containing:
//--- * CInfo[I,0]     -   size of I-th cluster
//--- * CInfo[I,1]     -   beginning of I-th cluster
//--- * CInfo[I,2]     -   end of I-th cluster
//--- * CInfo[I,3]     -   height of I-th cluster
//--- We perform it as follows:
//--- * first NPoints clusters have unit size (CInfo[I,0]=1) and zero
//---   height (CInfo[I,3]=0)
//--- * we replay NPoints-1 merges from first to last and fill sizes of
//---   corresponding clusters (new size is a sum of sizes of clusters
//---   being merged) and height (new height is max(heights)+1).
//--- * now we ready to determine locations of clusters. Last cluster
//---   spans entire dataset, we know it. We replay merges from last to
//---   first, during each merge we already know location of the merge
//---   result, and we can position first cluster to the left part of
//---   the result, and second cluster to the right part.
   rep.m_p.Resize(npoints);
   rep.m_pm.Resize(npoints-1,6);
   cinfo.Resize(2*npoints-1,4);
   for(i=0; i<npoints; i++)
     {
      cinfo.Set(i,0,1);
      cinfo.Set(i,3,0);
     }
   for(i=0; i<npoints-1; i++)
     {
      cinfo.Set(npoints+i,0,cinfo.Get(rep.m_z.Get(i,0),0)+cinfo.Get(rep.m_z.Get(i,1),0));
      cinfo.Set(npoints+i,3,MathMax(cinfo.Get(rep.m_z.Get(i,0),3),cinfo.Get(rep.m_z.Get(i,1),3))+1);
     }
   cinfo.Set(2*npoints-2,1,0);
   cinfo.Set(2*npoints-2,2,npoints-1);
   for(i=npoints-2; i>=0; i--)
     {
      //--- We merge C0 which spans [A0,B0] and C1 (spans [A1,B1]),
      //--- with unknown A0, B0, A1, B1. However, we know that result
      //--- is CR, which spans [AR,BR] with known AR/BR, and we know
      //--- sizes of C0, C1, CR (denotes as S0, S1, SR).
      c0=rep.m_z.Get(i,0);
      c1=rep.m_z.Get(i,1);
      s0=cinfo.Get(c0,0);
      s1=cinfo.Get(c1,0);
      ar=cinfo.Get(npoints+i,1);
      br=cinfo.Get(npoints+i,2);
      cinfo.Set(c0,1,ar);
      cinfo.Set(c0,2,ar+s0-1);
      cinfo.Set(c1,1,br-(s1-1));
      cinfo.Set(c1,2,br);
      rep.m_pm.Set(i,0,cinfo.Get(c0,1));
      rep.m_pm.Set(i,1,cinfo.Get(c0,2));
      rep.m_pm.Set(i,2,cinfo.Get(c1,1));
      rep.m_pm.Set(i,3,cinfo.Get(c1,2));
      rep.m_pm.Set(i,4,cinfo.Get(c0,3));
      rep.m_pm.Set(i,5,cinfo.Get(c1,3));
     }
   for(i=0; i<npoints; i++)
     {
      if(!CAp::Assert(cinfo.Get(i,1)==cinfo.Get(i,2)))
         return;
      rep.m_p.Set(i,cinfo.Get(i,1));
     }
//--- Calculate Rep.PZ
   rep.m_pz.Resize(npoints-1,2);
   for(i=0; i<npoints-1; i++)
     {
      rep.m_pz.Set(i,0,rep.m_z.Get(i,0));
      rep.m_pz.Set(i,1,rep.m_z.Get(i,1));
      if(rep.m_pz.Get(i,0)<npoints)
         rep.m_pz.Set(i,0,rep.m_p[rep.m_pz.Get(i,0)]);
      if(rep.m_pz.Get(i,1)<npoints)
         rep.m_pz.Set(i,1,rep.m_p[rep.m_pz.Get(i,1)]);
     }
  }
//+------------------------------------------------------------------+
//| This function recursively evaluates distance matrix  for         |
//| SOME(not all!) distance types.                                   |
//| INPUT PARAMETERS:                                                |
//|   XY          -  array[?, NFeatures], dataset                    |
//|   NFeatures   -  number of features, >= 1                        |
//|   DistType    -  distance function:                              |
//|                  *  0    Chebyshev distance(L - inf norm)        |
//|                  *  1    city block distance(L1 norm)            |
//|   D           -  preallocated output matrix                      |
//|   I0, I1      -  half interval of rows to calculate: [I0, I1) is |
//|                  processed                                       |
//|   J0, J1      -  half interval of cols to calculate: [J0, J1) is |
//|                  processed                                       |
//| OUTPUT PARAMETERS:                                               |
//|   D           -  array[NPoints, NPoints], distance matrix upper  |
//|                  triangle and main diagonal are initialized with |
//|                  data.                                           |
//| NOTE: intersection of [I0, I1) and [J0, J1) may completely lie   |
//|       in upper triangle, only partially intersect with it, or    |
//|       have zero intersection. In any case, only intersection of  |
//|       submatrix given by [I0, I1)*[J0, J1) with upper triangle   |
//|       of the matrix is evaluated.                                |
//|       Say, for 4x4 distance matrix A:                            |
//|   * [0, 2)*[0, 2) will result in evaluation of A00, A01, A11     |
//|   * [2, 4)*[2, 4) will result in evaluation of A22, A23, A32, A33|
//|   * [2, 4)*[0, 2) will result in evaluation of empty set of      |
//|                   elements                                       |
//+------------------------------------------------------------------+
void CClustering::EvaluateDistanceMatrixRec(CMatrixDouble &xy,
                                            int nfeatures,
                                            int disttype,
                                            CMatrixDouble &d,
                                            int i0,
                                            int i1,
                                            int j0,
                                            int j1)
  {
//--- create variables
   int    len0=0;
   int    len1=0;
   double v=0;
   double vv=0;
//--- check
   if(!CAp::Assert(disttype==0 || disttype==1,__FUNCTION__": incorrect DistType"))
      return;
//--- Normalize J0/J1:
//--- * J0:=max(J0,I0) - we ignore lower triangle
//--- * J1:=max(J1,J0) - normalize J1
   j0=MathMax(j0,i0);
   j1=MathMax(j1,j0);
   if(j1<=j0 || i1<=i0)
      return;
//--- Sequential processing
   for(int i=i0; i<i1; i++)
     {
      for(int j=j0; j<j1; j++)
        {
         if(j>=i)
           {
            v=0.0;
            if(disttype==0)
               for(int k=0; k<nfeatures; k++)
                 {
                  vv=MathAbs(xy.Get(i,k)-xy.Get(j,k));
                  if(vv>v)
                     v=vv;
                 }
            else
               if(disttype==1)
                  for(int k=0; k<nfeatures; k++)
                    {
                     vv=MathAbs(xy.Get(i,k)-xy.Get(j,k));
                     v+=vv;
                    }
            d.Set(i,j,v);
           }
        }
     }
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CFilters
  {
public:
   static void       FilterSMA(CRowDouble &x,int n,int k);
   static void       FilterEMA(CRowDouble &x,int n,double alpha);
   static void       FilterLRMA(CRowDouble &x,int n,int k);
  };
//+------------------------------------------------------------------+
//| Filters: simple moving averages (unsymmetric).                   |
//| This filter replaces array by results of SMA(K) filter. SMA(K)   |
//| is defined as filter which averages at most K previous points    |
//| (previous - not points AROUND central point) - or less, in case  |
//| of the first K-1 points.                                         |
//| INPUT PARAMETERS:                                                |
//|   X           -  array[N], array to process. It can be larger    |
//|                  than N, in this case only first N points are    |
//|                  processed.                                      |
//|   N           -  points count, N>=0                              |
//|   K           -  K>=1 (K can be larger than N, such cases will   |
//|                  be correctly handled). Window width. K=1        |
//|                  corresponds to identity transformation (nothing |
//|                  changes).                                       |
//| OUTPUT PARAMETERS:                                               |
//|   X           -  array, whose first N elements were processed    |
//|                  with SMA(K)                                     |
//| NOTE 1: this function uses efficient in-place algorithm which    |
//|         does not allocate temporary arrays.                      |
//| NOTE 2: this algorithm makes only one pass through array and     |
//|         uses running sum to speed-up calculation of the averages.|
//|         Additional measures are taken to ensure that running sum |
//|         on a long sequence of zero elements will be correctly    |
//|         reset to zero even in the presence of round-off error.   |
//| NOTE 3: this is unsymmetric version of the algorithm, which does |
//|         NOT averages points after the current one. Only          |
//|         X[i], X[i-1], ... are used when calculating new value    |
//|         of X[i]. We should also note that this algorithm uses    |
//|         BOTH previous points and  current  one,  i.e. new value  |
//|         of X[i] depends on BOTH previous point and X[i] itself.  |
//+------------------------------------------------------------------+
void CFilters::FilterSMA(CRowDouble &x,int n,int k)
  {
//--- Quick exit, if necessary
   if(n<=1 || k==1)
      return;
//--- create variables
   double runningsum=0;
   double termsinsum=0;
   int    zeroprefix=0;
   double v=0;
//--- check
   if(!CAp::Assert(n>=0,__FUNCTION__+": N<0"))
      return;
   if(!CAp::Assert(x.Size()>=n,__FUNCTION__+": Length(X)<N"))
      return;
   if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains INF or NAN"))
      return;
   if(!CAp::Assert(k>=1,__FUNCTION__+": K<1"))
      return;
//--- Prepare variables (see below for explanation)
   runningsum=0.0;
   termsinsum=0;
   for(int i=MathMax(n-k,0); i<n; i++)
     {
      runningsum=runningsum+x[i];
      termsinsum++;
     }
   int i=MathMax(n-k,0);
   zeroprefix=0;
   while(i<n && x[i]==0.0)
     {
      zeroprefix++;
      i++;
     }
//--- General case: we assume that N>1 and K>1
//--- Make one pass through all elements. At the beginning of
//--- the iteration we have:
//--- * I              element being processed
//--- * RunningSum     current value of the running sum
//---                  (including I-th element)
//--- * TermsInSum     number of terms in sum, 0<=TermsInSum<=K
//--- * ZeroPrefix     length of the sequence of zero elements
//---                  which starts at X[I-K+1] and continues towards X[I].
//---                  Equal to zero in case X[I-K+1] is non-zero.
//---                  This value is used to make RunningSum exactly zero
//---                  when it follows from the problem properties.
   for(i=n-1; i>=0; i--)
     {
      //--- Store new value of X[i], save old value in V
      v=x[i];
      x.Set(i,runningsum/termsinsum);
      //--- Update RunningSum and TermsInSum
      if(i-k>=0)
         runningsum+=x[i-k]-v;
      else
        {
         runningsum-=v;
         termsinsum --;
        }
      //--- Update ZeroPrefix.
      //--- In case we have ZeroPrefix=TermsInSum,
      //--- RunningSum is reset to zero.
      if(i-k>=0)
        {
         if(x[i-k]!=0.0)
            zeroprefix=0;
         else
            zeroprefix=MathMin(zeroprefix+1,k);
        }
      else
        {
         zeroprefix=MathMin(zeroprefix,i+1);
        }
      if(zeroprefix==termsinsum)
         runningsum=0;
     }
  }
//+------------------------------------------------------------------+
//| Filters: exponential moving averages.                            |
//| This filter replaces array by results of EMA(alpha) filter.      |
//| EMA(alpha) is defined as filter which replaces X[] by S[]:       |
//|      S[0] = X[0]                                                 |
//|      S[t] = alpha * X[t] + (1 - alpha) * S[t - 1]                |
//| INPUT PARAMETERS:                                                |
//|   X           -  array[N], array to process. It can be larger    |
//|                  than N, in this case only first N points are    |
//|                  processed.                                      |
//|   N           -  points count, N >= 0                            |
//|   alpha       -  0 < alpha <= 1, smoothing parameter.            |
//| OUTPUT PARAMETERS:                                               |
//|   X           -  array, whose first N elements were processed    |
//|                  with EMA(alpha)                                 |
//| NOTE 1: this function uses efficient in-place algorithm which    |
//|         does not allocate temporary arrays.                      |
//| NOTE 2: this algorithm uses BOTH previous points and current one,|
//|         i.e. new value of X[i] depends on BOTH previous point and|
//|         X[i] itself.                                             |
//| NOTE 3: technical analytis users quite often work with  EMA      |
//|         coefficient expressed in DAYS instead of fractions. If   |
//|         you want to calculate EMA(N), where N is a number of     |
//|         days, you can use alpha = 2 / (N + 1).                   |
//+------------------------------------------------------------------+
void CFilters::FilterEMA(CRowDouble &x,int n,double alpha)
  {
//--- Quick exit, if necessary
   if(n<=1 || alpha==1.0)
      return;
//--- check
   if(!CAp::Assert(n>=0,__FUNCTION__+": N<0"))
      return;
   if(!CAp::Assert(x.Size()>=n,__FUNCTION__+": Length(X)<N"))
      return;
   if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains INF or NAN"))
      return;
   if(!CAp::Assert(alpha>0.0,__FUNCTION__+": Alpha<=0"))
      return;
   if(!CAp::Assert(alpha<=1.0,__FUNCTION__+": Alpha>1"))
      return;
//--- Process
   for(int i=1; i<n; i++)
      x.Set(i,alpha*x[i]+(1-alpha)*x[i-1]);
  }
//+------------------------------------------------------------------+
//| Filters: linear regression moving averages.                      |
//| This filter replaces array by results of LRMA(K) filter.         |
//| LRMA(K) is defined as filter which, for each data point, builds  |
//| linear regression model using K prevous points (point itself is  |
//| included in these K points) and calculates value of this linear  |
//| model at the point in question.                                  |
//| INPUT PARAMETERS:                                                |
//|   X           -  array[N], array to process. It can be larger    |
//|                  than N, in this case only first N points are    |
//|                  processed.                                      |
//|   N           -  points count, N >= 0                            |
//|   K           -  K >= 1(K can be larger than N, such cases will  |
//|                  be correctly handled). Window width. K = 1      |
//|                  corresponds to identity transformation(nothing  |
//|                  changes).                                       |
//| OUTPUT PARAMETERS:                                               |
//|   X           -  array, whose first N elements were processed    |
//|                  with SMA(K)                                     |
//| NOTE 1: this function uses efficient in-place algorithm which    |
//|         does not allocate temporary arrays.                      |
//| NOTE 2: this algorithm makes only one pass through array and     |
//|         uses running sum to speed-up calculation of the averages.|
//|         Additional measures are taken to ensure that running sum |
//|         on a long sequence of zero elements will be correctly    |
//|         reset to zero even in the presence of round - off error. |
//| NOTE 3: this is unsymmetric version of the algorithm, which does |
//|         NOT averages points after the current one. Only          |
//|         X[i], X[i - 1], ... are used when calculating new value  |
//|         of X[i]. We should also note that this algorithm uses    |
//|         BOTH previous points and current one, i.e. new value of  |
//|         X[i] depends on BOTH previous point and X[i] itself.     |
//+------------------------------------------------------------------+
void CFilters::FilterLRMA(CRowDouble &x,int n,int k)
  {
//--- check
   if(!CAp::Assert(n>=0,__FUNCTION__+": N<0"))
      return;
   if(!CAp::Assert(x.Size()>=n,__FUNCTION__+": Length(X)<N"))
      return;
   if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains INF or NAN"))
      return;
   if(!CAp::Assert(k>=1,__FUNCTION__+": K<1"))
      return;
//--- Quick exit, if necessary:
//--- * either N is equal to 1 (nothing to average)
//--- * or K is 1 (only point itself is used) or 2 (model is too simple,
//---   we will always get identity transformation)
   if(n<=1 || k<=2)
      return;
//--- create variables
   CMatrixDouble xy;
   CRowDouble s;
   int    info=0;
   double a=0;
   double b=0;
   double vara=0;
   double varb=0;
   double covab=0;
   double corrab=0;
   double p=0;
   int    m=0;
   int    i1_=0;
//--- General case: K>2, N>1.
//--- We do not process points with I<2 because first two points (I=0 and I=1) will be
//--- left unmodified by LRMA filter in any case.
   xy.Resize(k,2);
   s=vector<double>::Ones(k);
   for(int i=0; i<k; i++)
      xy.Set(i,0,i);
   for(int i=n-1; i>=2; i--)
     {
      m=MathMin(i+1,k);
      i1_=i-m+1;
      for(int i_=0; i_<m; i_++)
         xy.Set(i_,1,x[i_+i1_]);
      CLinReg::LRLines(xy,s,m,info,a,b,vara,varb,covab,corrab,p);
      if(!CAp::Assert(info==1,__FUNCTION__+": internal error"))
         return;
      x.Set(i,a+b*(m-1));
     }
  }
//+------------------------------------------------------------------+
//| This object stores state of the SSA model.                       |
//| You should use ALGLIB functions to work with this object.        |
//+------------------------------------------------------------------+
struct CSSAModel
  {
   int               m_NSequences;
   CRowInt           m_SequenceIdx;
   CRowDouble        m_SequenceData;
   int               m_AlgoType;
   int               m_WindowWidth;
   int               m_RtPowerUp;
   int               m_TopK;
   int               m_PrecomputedWidth;
   int               m_PrecomputedNBasis;
   CMatrixDouble     m_PrecomputedBasis;
   int               m_DefaultSubspaceits;
   int               m_MemoryLimit;
   bool              m_AreBasisAndSolverValid;
   CMatrixDouble     m_Basis;
   CMatrixDouble     m_BasisT;
   CRowDouble        m_SV;
   CRowDouble        m_ForecastA;
   int               m_NBasis;
   CEigSubSpaceState m_Solver;
   CMatrixDouble     m_XXT;
   CHighQualityRandState m_RS;
   int               m_RngSeed;
   CRowInt           m_Rtqueue;
   int               m_RtqueueCnt;
   int               m_RtqueueChunk;
   int               m_DbgCntEVD;
   CRowDouble        m_Tmp0;
   CRowDouble        m_Tmp1;
   CEigSubSpaceReport m_SolverRep;
   CRowDouble        m_AlongTrend;
   CRowDouble        m_AlongNoise;
   CMatrixDouble     m_AseqTrajectory;
   CMatrixDouble     m_AseqTbProduct;
   CRowInt           m_AseqCounts;
   CRowDouble        m_FcTrend;
   CRowDouble        m_FcNoise;
   CMatrixDouble     m_FcTrendM;
   CMatrixDouble     m_UxBatch;
   int               m_UxBatchWidth;
   int               m_UxBatchSize;
   int               m_UxBatchLimit;
   //---
                     CSSAModel(void);
                    ~CSSAModel(void) {}
   void              Copy(const CSSAModel &obj);
   //--- overloading
   void              operator=(const CSSAModel &obj) { Copy(obj); }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CSSAModel::CSSAModel(void)
  {
   m_NSequences=0;
   m_AlgoType=0;
   m_WindowWidth=0;
   m_RtPowerUp=0;
   m_TopK=0;
   m_PrecomputedWidth=0;
   m_PrecomputedNBasis=0;
   m_DefaultSubspaceits=0;
   m_MemoryLimit=0;
   m_AreBasisAndSolverValid=false;
   m_NBasis=0;
   m_RngSeed=0;
   m_RtqueueCnt=0;
   m_RtqueueChunk=0;
   m_DbgCntEVD=0;
   m_UxBatchWidth=0;
   m_UxBatchSize=0;
   m_UxBatchLimit=0;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CSSAModel::Copy(const CSSAModel &obj)
  {
   m_NSequences=obj.m_NSequences;
   m_SequenceIdx=obj.m_SequenceIdx;
   m_SequenceData=obj.m_SequenceData;
   m_AlgoType=obj.m_AlgoType;
   m_WindowWidth=obj.m_WindowWidth;
   m_RtPowerUp=obj.m_RtPowerUp;
   m_TopK=obj.m_TopK;
   m_PrecomputedWidth=obj.m_PrecomputedWidth;
   m_PrecomputedNBasis=obj.m_PrecomputedNBasis;
   m_PrecomputedBasis=obj.m_PrecomputedBasis;
   m_DefaultSubspaceits=obj.m_DefaultSubspaceits;
   m_MemoryLimit=obj.m_MemoryLimit;
   m_AreBasisAndSolverValid=obj.m_AreBasisAndSolverValid;
   m_Basis=obj.m_Basis;
   m_BasisT=obj.m_BasisT;
   m_SV=obj.m_SV;
   m_ForecastA=obj.m_ForecastA;
   m_NBasis=obj.m_NBasis;
   m_Solver=obj.m_Solver;
   m_XXT=obj.m_XXT;
   m_RS=obj.m_RS;
   m_RngSeed=obj.m_RngSeed;
   m_Rtqueue=obj.m_Rtqueue;
   m_RtqueueCnt=obj.m_RtqueueCnt;
   m_RtqueueChunk=obj.m_RtqueueChunk;
   m_DbgCntEVD=obj.m_DbgCntEVD;
   m_Tmp0=obj.m_Tmp0;
   m_Tmp1=obj.m_Tmp1;
   m_SolverRep=obj.m_SolverRep;
   m_AlongTrend=obj.m_AlongTrend;
   m_AlongNoise=obj.m_AlongNoise;
   m_AseqTrajectory=obj.m_AseqTrajectory;
   m_AseqTbProduct=obj.m_AseqTbProduct;
   m_AseqCounts=obj.m_AseqCounts;
   m_FcTrend=obj.m_FcTrend;
   m_FcNoise=obj.m_FcNoise;
   m_FcTrendM=obj.m_FcTrendM;
   m_UxBatch=obj.m_UxBatch;
   m_UxBatchWidth=obj.m_UxBatchWidth;
   m_UxBatchSize=obj.m_UxBatchSize;
   m_UxBatchLimit=obj.m_UxBatchLimit;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CSSA
  {
public:
   static void       SSACreate(CSSAModel &s);
   static void       SSASetWindow(CSSAModel &s,int windowwidth);
   static void       SSASetSeed(CSSAModel &s,int seed);
   static void       SSASetPowerUpLength(CSSAModel &s,int pwlen);
   static void       SSASetMemoryLimit(CSSAModel &s,int memlimit);
   static void       SSAAddSequence(CSSAModel &s,CRowDouble &x,int n);
   static void       SSAAppendPointAndUpdate(CSSAModel &s,double x,double updateits);
   static void       SSAAppendSequenceAndUpdate(CSSAModel &s,CRowDouble &x,int nticks,double updateits);
   static void       SSASetAlgoPrecomputed(CSSAModel &s,CMatrixDouble &a,int windowwidth,int nbasis);
   static void       SSASetAlgoTopKDirect(CSSAModel &s,int topk);
   static void       SSASetAlgoTopKRealtime(CSSAModel &s,int topk);
   static void       SSAClearData(CSSAModel &s);
   static void       SSAGetBasis(CSSAModel &s,CMatrixDouble &a,CRowDouble &sv,int &windowwidth,int &nbasis);
   static void       SSAGetLRR(CSSAModel &s,CRowDouble &a,int &windowwidth);
   static void       SSAAnalyzeLastWindow(CSSAModel &s,CRowDouble &trend,CRowDouble &noise,int &nticks);
   static void       SSAAnalyzeLast(CSSAModel &s,int nticks,CRowDouble &trend,CRowDouble &noise);
   static void       SSAAnalyzeSequence(CSSAModel &s,CRowDouble &data,int nticks,CRowDouble &trend,CRowDouble &noise);
   static void       SSAForecastLast(CSSAModel &s,int nticks,CRowDouble &trend);
   static void       SSAForecastSequence(CSSAModel &s,CRowDouble &data,int datalen,int forecastlen,bool applysmoothing,CRowDouble &trend);
   static void       SSAForecastAvgLast(CSSAModel &s,int m,int nticks,CRowDouble &trend);
   static void       SSAForecastAvgSequence(CSSAModel &s,CRowDouble &data,int datalen,int m,int forecastlen,bool applysmoothing,CRowDouble &trend);

private:
   static bool       HasSomethingToAnalyze(CSSAModel &s);
   static bool       IsSequenceBigEnough(CSSAModel &s,int i);
   static void       UpdateBasis(CSSAModel &s,int appendlen,double updateits);
   static void       AnalyzeSequence(CSSAModel &s,CRowDouble &data,int i0,int i1,CRowDouble &trend,CRowDouble &noise,int offs);
   static void       ForecastAvgSequence(CSSAModel &s,CRowDouble &data,int i0,int i1,int m,int forecastlen,bool smooth,CRowDouble &trend,int offs);
   static void       RealtimeDequeue(CSSAModel &s,double beta,int cnt);
   static void       UpdateXXTPrepare(CSSAModel &s,int updatesize,int windowwidth,int memorylimit);
   static void       UpdateXXTSend(CSSAModel &s,CRowDouble &u,int i0,CMatrixDouble &xxt);
   static void       UpdateXXTFinalize(CSSAModel &s,CMatrixDouble &xxt);
  };
//+------------------------------------------------------------------+
//| This function creates SSA model object. Right after creation     |
//| model is in "dummy" mode - you can add data, but analyzing /     |
//| prediction will return just zeros (it assumes that basis is      |
//| empty).                                                          |
//| HOW TO USE SSA MODEL:                                            |
//|   1. create model with SSACreate()                               |
//|   2. add data with one/many SSAAddSequence() calls               |
//|   3. choose SSA algorithm with one of SSASetAlgo...() functions: |
//|      * SSASetAlgoTopKDirect() for direct one-run analysis        |
//|      * SSASetAlgoTopKRealtime() for algorithm optimized for many |
//|         subsequent runs with warm-start capabilities             |
//|      * SSASetAlgoPrecomputed() for user-supplied basis           |
//|   4. set window width with SSASetWindow()                        |
//|   5. perform one of the analysis-related activities:             |
//|      a) call SSAGetBasis() to get basis                          |
//|      b) call SSAAnalyzeLast() SSAAnalyzeSequence() or            |
//|         SSAAnalyzeLastWindow() to perform analysis (trend/noise  |
//|         separation)                                              |
//|      c) call one of the forecasting functions (SSAForecastLast() |
//|         or SSAForecastSequence()) to perform prediction;         |
//|         alternatively, you can extract linear recurrence         |
//|         coefficients with SSAGetLRR().                           |
//| SSA analysis will be performed during first call to analysis -   |
//| related function. SSA model is smart enough to track all changes |
//| in the dataset and model settings, to cache previously computed  |
//| basis and to re-evaluate basis only when necessary.              |
//| Additionally, if your setting involves constant stream  of       |
//| incoming data, you can perform quick update already calculated   |
//| model with one of the incremental append-and-update  functions:  |
//| SSAAppendPointAndUpdate() or SSAAppendSequenceAndUpdate().       |
//| NOTE: steps (2), (3), (4) can be performed in arbitrary order.   |
//| INPUT PARAMETERS:                                                |
//|      none                                                        |
//| OUTPUT PARAMETERS:                                               |
//|   S           -  structure which stores model state              |
//+------------------------------------------------------------------+
void CSSA::SSACreate(CSSAModel &s)
  {
//--- Model data, algorithms and settings
   s.m_NSequences=0;
   s.m_SequenceIdx.Resize(1);
   s.m_SequenceIdx.Set(0,0);
   s.m_AlgoType=0;
   s.m_WindowWidth=1;
   s.m_RtPowerUp=1;
   s.m_AreBasisAndSolverValid=false;
   s.m_RngSeed=1;
   s.m_DefaultSubspaceits=10;
   s.m_MemoryLimit=50000000;
//--- Debug counters
   s.m_DbgCntEVD=0;
  }
//+------------------------------------------------------------------+
//| This function sets window width for SSA model. You should call it|
//| before analysis phase. Default window width is 1 (not for real   |
//| use).                                                            |
//| Special notes:                                                   |
//|   * this function call can be performed at any moment before     |
//|     first call to analysis-related functions                     |
//|   * changing window width invalidates internally stored basis;   |
//|     if you change window width AFTER you call analysis-related   |
//|     function, next analysis phase will require re-calculation of |
//|     the basis according to current algorithm.                    |
//|   * calling this function with exactly same window width as      |
//|     current one has no effect                                    |
//|   * if you specify window width larger than any data sequence    |
//|     stored in the model, analysis will return zero basis.        |
//| INPUT PARAMETERS:                                                |
//|   S           -   SSA model created with SSACreate()             |
//|   WindowWidth -   >=1, new window width                          |
//| OUTPUT PARAMETERS:                                               |
//|   S           -  SSA model, updated                              |
//+------------------------------------------------------------------+
void CSSA::SSASetWindow(CSSAModel &s,int windowwidth)
  {
//--- check
   if(!CAp::Assert(windowwidth>=1,__FUNCTION__+": WindowWidth<1"))
      return;
   if(windowwidth==s.m_WindowWidth)
      return;

   s.m_WindowWidth=windowwidth;
   s.m_AreBasisAndSolverValid=false;
  }
//+------------------------------------------------------------------+
//| This function sets seed which is used to initialize internal RNG |
//| when we make pseudorandom decisions on model updates.            |
//| By default, deterministic seed is used - which results in same   |
//| sequence of pseudorandom decisions every time you run SSA model. |
//| If you specify non-deterministic seed value, then SSA model may  |
//| return slightly different results after each run.                |
//| This function can be useful when you have several SSA models     |
//| updated with SSAAppendPointAndUpdate() called with 0<UpdateIts<1 |
//| (fractional value) and due to performance limitations want them  |
//| to perform updates at different moments.                         |
//| INPUT PARAMETERS:                                                |
//|   S           -  SSA model                                       |
//|   Seed        -  seed:                                           |
//|                  * positive values = use deterministic seed for  |
//|                    each run of algorithms which depend on random |
//|                    initialization                                |
//|                  * zero or negative values=use non-deterministic |
//|                    seed                                          |
//+------------------------------------------------------------------+
void CSSA::SSASetSeed(CSSAModel &s,int seed)
  {
   s.m_RngSeed=seed;
  }
//+------------------------------------------------------------------+
//| This function sets length of power-up cycle for real-time        |
//| algorithm.                                                       |
//| By default, this algorithm performs costly O(N*WindowWidth^2)    |
//| init phase followed by full run of truncated EVD. However, if you|
//| are ready to live with a bit lower-quality basis during first few|
//| iterations, you can split this O(N*WindowWidth^2) initialization |
//| between several subsequent append-and-update rounds. It results  |
//| in better latency of the algorithm.                              |
//| This function invalidates basis/solver, next analysis call will  |
//| result in full recalculation of everything.                      |
//| INPUT PARAMETERS:                                                |
//|   S           -  SSA model                                       |
//|   PWLen       -  length of the power-up stage:                   |
//|                  * 0 means that no power-up is requested         |
//|                  * 1 is the same as 0                            |
//|                  * >1 means that delayed power-up is performed   |
//+------------------------------------------------------------------+
void CSSA::SSASetPowerUpLength(CSSAModel &s,int pwlen)
  {
   if(!CAp::Assert(pwlen>=0,__FUNCTION__+": PWLen<0"))
      return;

   s.m_RtPowerUp=MathMax(pwlen,1);
   s.m_AreBasisAndSolverValid=false;
  }
//+------------------------------------------------------------------+
//| This function sets memory limit of SSA analysis.                 |
//| Straightforward SSA with sequence length T and window width W    |
//| needs O(T*W) memory. It is possible to reduce memory consumption |
//| by splitting task into smaller chunks.                           |
//| Thus function allows you to specify approximate memory limit     |
//| (measured in double precision numbers used for buffers). Actual  |
//| memory consumption will be comparable to the number specified by |
//| you.                                                             |
//| Default memory limit is 50.000.000 (400Mbytes) in current        |
//| version.                                                         |
//| INPUT PARAMETERS:                                                |
//|   S        -  SSA model                                          |
//|   MemLimit -  memory limit, >=0. Zero value means no limit.      |
//+------------------------------------------------------------------+
void CSSA::SSASetMemoryLimit(CSSAModel &s,int memlimit)
  {
   if(memlimit<0)
      memlimit=0;
   s.m_MemoryLimit=memlimit;
  }
//+------------------------------------------------------------------+
//| This function adds data sequence to SSA model. Only single-      |
//| dimensional sequences are supported.                             |
//| What is a sequences? Following definitions/requirements apply:   |
//|   * a sequence is an array of values measured in subsequent,     |
//|     equally separated time moments (ticks).                      |
//|   * you may have many sequences in your dataset; say, one        |
//|     sequence may correspond to one trading session.              |
//|   * sequence length should be larger than current window length  |
//|     (shorter sequences will be ignored during analysis).         |
//|   * analysis is performed within a sequence; different sequences |
//|     are NOT stacked together to produce one large contiguous     |
//|     stream of data.                                              |
//|   * analysis is performed for all sequences at once, i.e. same   |
//|     set of basis vectors is computed for all sequences           |
//| INCREMENTAL ANALYSIS                                             |
//| This function is non intended for incremental updates of         |
//| previously found SSA basis. Calling it invalidates all previous  |
//| analysis results (basis is reset and will be recalculated from   |
//| zero during next analysis).                                      |
//| If you want to perform incremental/real-time SSA, consider using |
//| following functions:                                             |
//|   * SSAAppendPointAndUpdate() for appending one point            |
//|   * SSAAppendSequenceAndUpdate() for appending new sequence      |
//| INPUT PARAMETERS:                                                |
//|   S           -  SSA model created with SSACreate()              |
//|   X           -  array[N], data, can be larger (additional values|
//|                  are ignored)                                    |
//|   N           -  data length, can be automatically determined    |
//|                  from the array length. N>=0.                    |
//| OUTPUT PARAMETERS:                                               |
//|   S           -  SSA model, updated                              |
//| NOTE: you can clear dataset with SSAClearData()                  |
//+------------------------------------------------------------------+
void CSSA::SSAAddSequence(CSSAModel &s,CRowDouble &x,int n)
  {
//--- check
   if(!CAp::Assert(n>=0,__FUNCTION__+": N<0"))
      return;
   if(!CAp::Assert(x.Size()>=n,__FUNCTION__+": X is too short"))
      return;
   if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinities NANs"))
      return;
//--- Invalidate model
   s.m_AreBasisAndSolverValid=false;
//--- Add sequence
   s.m_SequenceIdx.Resize(s.m_NSequences+2);
   s.m_SequenceIdx.Set(s.m_NSequences+1,s.m_SequenceIdx[s.m_NSequences]+n);
   s.m_SequenceData.Resize(s.m_SequenceIdx[s.m_NSequences+1]);

   int offs=s.m_SequenceIdx[s.m_NSequences];
   for(int i=0; i<n; i++)
      s.m_SequenceData.Set(offs+i,x[i]);
   s.m_NSequences++;
  }
//+------------------------------------------------------------------+
//| This function appends single point to last data sequence stored  |
//| in the SSA model and tries to update model in the incremental    |
//| manner (if possible with current algorithm).                     |
//| If you want to add more than one point at once:                  |
//|   * if you want to add M points to the same sequence, perform    |
//|     M-1 calls with UpdateIts parameter set to 0.0, and last call |
//|     with non-zero UpdateIts.                                     |
//|   * if you want to add new sequence, use                         |
//|     SSAAppendSequenceAndUpdate()                                 |
//| Running time of this function does NOT depend on dataset size,   |
//| only on window width and number of singular vectors. Depending   |
//| on algorithm being used, incremental update has complexity:      |
//|   * for top-K real time   -  O(UpdateIts*K*Width^2), with        |
//|                              fractional UpdateIts                |
//|   * for top-K direct      -  O(Width^3) for any non-zero         |
//|                              UpdateIts                           |
//|   * for precomputed basis -  O(1), no update is performed        |
//| INPUT PARAMETERS:                                                |
//|   S           -  SSA model created with SSACreate()              |
//|   X           -  new point                                       |
//|   UpdateIts   -  >=0, floating point(!) value, desired update    |
//|                  frequency:                                      |
//|            * zero value means that point is stored, but no update|
//|              is performed                                        |
//|            * integer part of the value means that specified      |
//|              number of iterations is always performed            |
//|            * fractional part of the value means that one         |
//|              iteration is performed with this probability.       |
//| Recommended value: 0<UpdateIts<=1. Values larger than 1 are VERY |
//| seldom needed. If your dataset changes slowly, you can set it to |
//| 0.1 and skip 90% of updates.                                     |
//| In any case, no information is lost even with zero value of      |
//| UpdateIts! It will be incorporated into model, sooner or later.  |
//| OUTPUT PARAMETERS:                                               |
//|   S           -  SSA model, updated                              |
//| NOTE: this function uses internal RNG to handle fractional values|
//|       of UpdateIts. By default it is initialized with fixed seed |
//|       during initial calculation of basis. Thus subsequent calls |
//|       to this function will result in the same sequence of       |
//|       pseudorandom decisions.                                    |
//| However, if you have several SSA models which are calculated     |
//| simultaneously, and if you want to reduce computational          |
//| bottlenecks by performing random updates at random moments, then |
//| fixed seed is not an option - all updates will fire at same      |
//| moments.                                                         |
//| You may change it with SSASetSeed() function.                    |
//| NOTE: this function throws an exception if called for empty      |
//|       dataset (there is no "last" sequence to modify).           |
//+------------------------------------------------------------------+
void CSSA::SSAAppendPointAndUpdate(CSSAModel &s,double x,double updateits)
  {
//--- check
   if(!CAp::Assert(MathIsValidNumber(x),__FUNCTION__+": X is not finite"))
      return;
//--- check
   if(!CAp::Assert(MathIsValidNumber(updateits),__FUNCTION__+": UpdateIts is not finite"))
      return;
//--- check
   if(!CAp::Assert(updateits>=0.0,__FUNCTION__+": UpdateIts<0"))
      return;
//--- check
   if(!CAp::Assert(s.m_NSequences>0,__FUNCTION__+": dataset is empty,no sequence to modify"))
      return;
//--- Append point to dataset
   s.m_SequenceData.Resize(s.m_SequenceIdx[s.m_NSequences]+1);
   s.m_SequenceData.Set(s.m_SequenceIdx[s.m_NSequences],x);
   s.m_SequenceIdx.Set(s.m_NSequences,s.m_SequenceIdx[s.m_NSequences]+1);
//--- Do we have something to analyze? If no, invalidate basis
//--- (just to be sure) and exit.
   if(!HasSomethingToAnalyze(s))
     {
      s.m_AreBasisAndSolverValid=false;
      return;
     }
//--- Well, we have data to analyze and algorithm set, but basis is
//--- invalid. Let's calculate it from scratch and exit.
   if(!s.m_AreBasisAndSolverValid)
     {
      UpdateBasis(s,0,0.0);
      return;
     }
//--- Update already computed basis
   UpdateBasis(s,1,updateits);
  }
//+------------------------------------------------------------------+
//| This function appends new sequence to dataset stored in the SSA  |
//| model and tries to update model in the incremental manner (if    |
//| possible with current algorithm).                                |
//| Notes:                                                           |
//|   * if you want to add M sequences at once, perform M-1 calls    |
//|     with UpdateIts parameter set to 0.0, and last call with      |
//|     non-zero UpdateIts.                                          |
//|   * if you want to add just one point, use                       |
//|     SSAAppendPointAndUpdate()                                    |
//| Running time of this function does NOT depend on dataset size,   |
//| only on sequence length, window width and number of singular     |
//| vectors. Depending on algorithm being used, incremental update   |
//| has complexity:                                                  |
//|   * for top-K real time   -  O(UpdateIts*K*Width^2+              |
//|                                (NTicks-Width)*Width^2)           |
//|   * for top-K direct      -  O(Width^3+(NTicks-Width)*Width^2)   |
//|   * for precomputed basis -  O(1), no update is performed        |
//| INPUT PARAMETERS:                                                |
//|   S           -  SSA model created with SSACreate()              |
//|   X           -  new sequence, array[NTicks] or larget           |
//|   NTicks      -  >=1, number of ticks in the sequence            |
//|   UpdateIts   -  >=0, floating point(!) value, desired update    |
//|                  frequency:                                      |
//|               * zero value means that point is stored, but no    |
//|                 update is performed                              |
//|               * integer part of the value means  that  specified |
//|                 number of iterations is always performed         |
//|               * fractional part of the value means that one      |
//|                 iteration is performed with this probability.    |
//| Recommended value: 0<UpdateIts<=1. Values larger than 1 are VERY |
//| seldom needed. If your dataset changes slowly, you can set it to |
//| 0.1 and skip 90% of updates.                                     |
//| In any case, no information is lost even with zero value of      |
//| UpdateIts! It will be incorporated into model, sooner or later.  |
//| OUTPUT PARAMETERS:                                               |
//|   S           -  SSA model, updated                              |
//| NOTE: this function uses internal RNG to handle fractional values|
//|       of UpdateIts. By default it is initialized with fixed seed |
//|       during initial calculation of basis. Thus subsequent calls |
//|       to this function will result in the same sequence of       |
//|       pseudorandom decisions.                                    |
//| However, if you have several SSA models which are calculated     |
//| simultaneously, and if you want to reduce computational          |
//| bottlenecks by performing random updates at random moments, then |
//| fixed seed is not an option - all updates will fire at same      |
//| moments.                                                         |
//| You may change it with SSASetSeed() function.                    |
//+------------------------------------------------------------------+
void CSSA::SSAAppendSequenceAndUpdate(CSSAModel &s,CRowDouble &x,
                                      int nticks,double updateits)
  {
//--- check
   if(!CAp::Assert(nticks>=0,__FUNCTION__+": NTicks<0"))
      return;
   if(!CAp::Assert(x.Size()>=nticks,__FUNCTION__+": X is too short"))
      return;
   if(!CAp::Assert(CApServ::IsFiniteVector(x,nticks),__FUNCTION__+": X contains infinities NANs"))
      return;
//--- Add sequence
   s.m_SequenceIdx.Resize(s.m_NSequences+2);
   s.m_SequenceIdx.Set(s.m_NSequences+1,s.m_SequenceIdx[s.m_NSequences]+nticks);
   s.m_SequenceData.Resize(s.m_SequenceIdx[s.m_NSequences+1]);

   int offs=s.m_SequenceIdx[s.m_NSequences];
   for(int i=0; i<nticks; i++)
      s.m_SequenceData.Set(offs+i,x[i]);
   s.m_NSequences++;
//--- Do we have something to analyze? If no, invalidate basis
//--- (just to be sure) and exit.
   if(!HasSomethingToAnalyze(s))
     {
      s.m_AreBasisAndSolverValid=false;
      return;
     }
//--- Well, we have data to analyze and algorithm set, but basis is
//--- invalid. Let's calculate it from scratch and exit.
   if(!s.m_AreBasisAndSolverValid)
     {
      UpdateBasis(s,0,0.0);
      return;
     }
//--- Update already computed basis
   if(nticks>=s.m_WindowWidth)
      UpdateBasis(s,nticks-s.m_WindowWidth+1,updateits);
  }
//+------------------------------------------------------------------+
//| This function sets SSA algorithm to "precomputed vectors"        |
//| algorithm.                                                       |
//| This algorithm uses precomputed set of orthonormal(orthogonal    |
//| AND normalized) basis vectors supplied by user. Thus, basis      |
//| calculation phase is not performed - we already have our basis - |
//| and only analysis/forecasting phase requires actual calculations.|
//| This algorithm may handle "append" requests which add just one/  |
//| few ticks to the end of the last sequence in O(1) time.          |
//| NOTE: this algorithm accepts both basis and window width, because|
//|       these two parameters are naturally aligned. Calling this   |
//|       function sets window width; if you call SSASetWindow()     |
//|       with other window width, then during analysis stage        |
//|       algorithm will detect conflict and reset to zero basis.    |
//| INPUT PARAMETERS:                                                |
//|   S           -  SSA model                                       |
//|   A           -  array[WindowWidth, NBasis], orthonormalized     |
//|                  basis; this function does NOT control           |
//|                  orthogonality and does NOT perform any kind of  |
//|                  renormalization. It is your responsibility to   |
//|                  provide it with correct basis.                  |
//|   WindowWidth -  window width, >= 1                              |
//|   NBasis      -  number of basis vectors,                        |
//|                  1 <= NBasis <= WindowWidth                      |
//| OUTPUT PARAMETERS:                                               |
//|   S           -  updated model                                   |
//| NOTE: calling this function invalidates basis in all cases.      |
//+------------------------------------------------------------------+
void CSSA::SSASetAlgoPrecomputed(CSSAModel &s,CMatrixDouble &a,
                                 int windowwidth,int nbasis)
  {
//--- check
   if(!CAp::Assert(windowwidth>=1,__FUNCTION__+": WindowWidth<1"))
      return;
   if(!CAp::Assert(nbasis>=1,__FUNCTION__+": NBasis<1"))
      return;
   if(!CAp::Assert(nbasis<=windowwidth,__FUNCTION__+": NBasis>WindowWidth"))
      return;
   if(!CAp::Assert(a.Rows()>=windowwidth,__FUNCTION__+": Rows(A)<WindowWidth"))
      return;
   if(!CAp::Assert(a.Cols()>=nbasis,__FUNCTION__+": Rows(A)<NBasis"))
      return;
   if(!CAp::Assert(CApServ::IsFiniteMatrix(a,windowwidth,nbasis),__FUNCTION__+": Rows(A)<NBasis"))
      return;

   s.m_AlgoType=1;
   s.m_PrecomputedWidth=windowwidth;
   s.m_PrecomputedNBasis=nbasis;
   s.m_WindowWidth=windowwidth;
   s.m_PrecomputedBasis=a;
   s.m_PrecomputedBasis.Resize(windowwidth,nbasis);
   s.m_AreBasisAndSolverValid=false;
  }
//+------------------------------------------------------------------+
//| This function sets SSA algorithm to "direct top-K" algorithm.    |
//| "Direct top-K" algorithm performs full SVD of the N*WINDOW       |
//| trajectory matrix (hence its name - direct solver is used), then |
//| extracts top K components. Overall running time is               |
//| O(N * WINDOW ^ 2), where N is a number of ticks in the dataset,  |
//| WINDOW is window width.                                          |
//| This algorithm may handle "append" requests which add just one / |
//| few ticks to the end of the last sequence in O(WINDOW ^ 3) time, |
//| which is ~N/WINDOW times faster than re-computing everything from|
//| scratch.                                                         |
//| INPUT PARAMETERS:                                                |
//|   S           -  SSA model                                       |
//|   TopK        -  number of components to analyze; TopK >= 1.     |
//| OUTPUT PARAMETERS:                                               |
//|   S           -  updated model                                   |
//| NOTE: TopK>WindowWidth is silently decreased to WindowWidth      |
//|       during analysis phase                                      |
//| NOTE: calling this function invalidates basis, except for the    |
//|       situation when this algorithm was already set with same    |
//|       parameters.                                                |
//+------------------------------------------------------------------+
void CSSA::SSASetAlgoTopKDirect(CSSAModel &s,int topk)
  {
//--- check
   if(!CAp::Assert(topk>=1,__FUNCTION__+": TopK<1"))
      return;
//--- Ignore calls which change nothing
   if(s.m_AlgoType==2 && s.m_TopK==topk)
      return;
//--- Update settings, invalidate model
   s.m_AlgoType=2;
   s.m_TopK=topk;
   s.m_AreBasisAndSolverValid=false;
  }
//+------------------------------------------------------------------+
//| This function sets SSA algorithm to "top-K real time algorithm". |
//| This algo extracts K components with largest singular values.    |
//| It is real-time version of top-K algorithm which is optimized for|
//| incremental processing and fast start-up. Internally it uses     |
//| subspace eigensolver for truncated SVD. It results in ability to |
//| perform quick updates of the basis when only a few points /      |
//| sequences is added to dataset.                                   |
//| Performance profile of the algorithm is given below:             |
//|   * O(K * WindowWidth ^ 2) running time for incremental update   |
//|     of the dataset with one of the "append-and-update" functions |
//|     (SSAAppendPointAndUpdate() or SSAAppendSequenceAndUpdate()). |
//|   * O(N * WindowWidth ^ 2) running time for initial basis        |
//|     evaluation(N = size  of dataset)                             |
//|   * ability to split costly initialization across several        |
//|     incremental updates of the basis(so called "Power-Up"        |
//|     functionality, activated by SSASetPowerUpLength() function)  |
//| INPUT PARAMETERS:                                                |
//|   S           -  SSA model                                       |
//|   TopK        -  number of components to analyze; TopK >= 1.     |
//| OUTPUT PARAMETERS:                                               |
//|   S           -  updated model                                   |
//| NOTE: this algorithm is optimized for large-scale tasks with     |
//|       large datasets. On toy problems with just  5-10 points it  |
//|       can return basis which is slightly different from that     |
//|       returned by direct algorithm (SSASetAlgoTopKDirect()       |
//|       function). However, the difference becomes negligible as   |
//|       dataset grows.                                             |
//| NOTE: TopK > WindowWidth is silently decreased to WindowWidth    |
//|       during analysis phase                                      |
//| NOTE: calling this function invalidates basis, except for the    |
//|       situation when this algorithm was already set with same    |
//|       parameters.                                                |
//+------------------------------------------------------------------+
void CSSA::SSASetAlgoTopKRealtime(CSSAModel &s,int topk)
  {
//--- check
   if(!CAp::Assert(topk>=1,__FUNCTION__+": TopK<1"))
      return;
//--- Ignore calls which change nothing
   if(s.m_AlgoType==3 && s.m_TopK==topk)
      return;
//--- Update settings, invalidate model
   s.m_AlgoType=3;
   s.m_TopK=topk;
   s.m_AreBasisAndSolverValid=false;
  }
//+------------------------------------------------------------------+
//| This function clears all data stored in the model and invalidates|
//| all basis components found so far.                               |
//| INPUT PARAMETERS:                                                |
//|   S        -  SSA model created with SSACreate()                 |
//| OUTPUT PARAMETERS:                                               |
//|   S        -  SSA model, updated                                 |
//+------------------------------------------------------------------+
void CSSA::SSAClearData(CSSAModel &s)
  {
   s.m_NSequences=0;
   s.m_AreBasisAndSolverValid=false;
  }
//+------------------------------------------------------------------+
//| This function executes SSA on internally stored dataset and      |
//| returns basis found by current method.                           |
//| INPUT PARAMETERS:                                                |
//|   S        -  SSA model                                          |
//| OUTPUT PARAMETERS:                                               |
//|   A        -  array[WindowWidth, NBasis], basis; vectors are     |
//|               stored in matrix columns, by descreasing variance  |
//|   SV       -  array[NBasis]:                                     |
//|            * zeros - for model initialized with                  |
//|              SSASetAlgoPrecomputed()                             |
//|            * singular values - for other algorithms              |
//|   WindowWidth -  current window                                  |
//|   NBasis   -  basis size                                         |
//| CACHING / REUSE OF THE BASIS                                     |
//| Caching / reuse of previous results is performed:                |
//|   * first call performs full run of SSA; basis is stored in the  |
//|     cache                                                        |
//|   * subsequent calls reuse previously cached basis               |
//|   * if you call any function which changes model properties      |
//|     (window length, algorithm, dataset), internal basis will be  |
//|     invalidated.                                                 |
//|   * the only calls which do NOT invalidate basis are listed      |
//|     below:                                                       |
//|      a) SSASetWindow() with same window length                   |
//|      b) SSAAppendPointAndUpdate()                                |
//|      c) SSAAppendSequenceAndUpdate()                             |
//|      d) SSASetAlgoTopK...() with exactly same K Calling these    |
//|         functions will result in reuse of previously found basis.|
//| HANDLING OF DEGENERATE CASES                                     |
//| Calling this function in degenerate cases(no data or all data are|
//| shorter than window size; no algorithm is specified) returns     |
//| basis with just one zero vector.                                 |
//+------------------------------------------------------------------+
void CSSA::SSAGetBasis(CSSAModel &s,CMatrixDouble &a,
                       CRowDouble &sv,int &windowwidth,
                       int &nbasis)
  {
//--- initialization
   windowwidth=0;
   nbasis=0;
//--- Is it degenerate case?
   if(!HasSomethingToAnalyze(s))
     {
      windowwidth=s.m_WindowWidth;
      nbasis=1;
      a=matrix<double>::Zeros(windowwidth,1);
      sv=vector<double>::Zeros(1);
      return;
     }
//--- Update basis.
//--- It will take care of basis validity flags. AppendLen=0 which means
//--- that we perform initial basis evaluation.
   UpdateBasis(s,0,0.0);
//--- check
   if(!CAp::Assert(s.m_NBasis>0,__FUNCTION__+": integrity check failed"))
      return;
   if(!CAp::Assert(s.m_WindowWidth>0,__FUNCTION__+": integrity check failed"))
      return;
//--- Output
   nbasis=s.m_NBasis;
   windowwidth=s.m_WindowWidth;
   a=s.m_Basis;
   sv=s.m_SV;
   a.Resize(windowwidth,nbasis);
   sv.Resize(nbasis);
  }
//+------------------------------------------------------------------+
//| This function returns linear recurrence relation(LRR)            |
//| coefficients found by current SSA algorithm.                     |
//| INPUT PARAMETERS:                                                |
//|   S        -  SSA model                                          |
//| OUTPUT PARAMETERS:                                               |
//|   A        -  array[WindowWidth - 1]. Coefficients of the linear |
//|               recurrence of the form:                            |
//|               X[W - 1] = X[W - 2] * A[W - 2] +                   |
//|                          X[W - 3] * A[W - 3] + ... + X[0] * A[0].|
//|               Empty array for WindowWidth = 1.                   |
//|   WindowWidth -  current window width                            |
//| CACHING / REUSE OF THE BASIS                                     |
//| Caching / reuse of previous results is performed:                |
//|   * first call performs full run of SSA; basis is stored in the  |
//|     cache                                                        |
//|   * subsequent calls reuse previously cached basis               |
//|   * if you call any function which changes model properties      |
//|     (window length, algorithm, dataset), internal basis will be  |
//|     invalidated.                                                 |
//|   * the only calls which do NOT invalidate basis are listed      |
//|     below:                                                       |
//|      a) SSASetWindow() with same window length                   |
//|      b) SSAAppendPointAndUpdate()                                |
//|      c) SSAAppendSequenceAndUpdate()                             |
//|      d) SSASetAlgoTopK...() with exactly same K                  |
//| Calling these functions will result in reuse of previously found |
//| basis.                                                           |
//| HANDLING OF DEGENERATE CASES                                     |
//| Calling this function in degenerate cases (no data or all data   |
//| are shorter than window size; no algorithm is specified) returns |
//| zeros.                                                           |
//+------------------------------------------------------------------+
void CSSA::SSAGetLRR(CSSAModel &s,CRowDouble &a,int &windowwidth)
  {
   a.Resize(0);
   windowwidth=0;
//--- check
   if(!CAp::Assert(s.m_WindowWidth>0,__FUNCTION__+": integrity check failed"))
      return;
//--- Is it degenerate case?
   if(!HasSomethingToAnalyze(s))
     {
      windowwidth=s.m_WindowWidth;
      a=vector<double>::Zeros(windowwidth-1);
      return;
     }
//--- Update basis.
//--- It will take care of basis validity flags. AppendLen=0 which means
//--- that we perform initial basis evaluation.
   UpdateBasis(s,0,0.0);
//--- Output
   windowwidth=s.m_WindowWidth;
   a=s.m_ForecastA;
   a.Resize(windowwidth-1);
  }
//+------------------------------------------------------------------+
//| This function executes SSA on internally stored dataset and      |
//| returns analysis for the last window of the last sequence. Such  |
//| analysis is an lightweight alternative for full scale            |
//| reconstruction (see below).                                      |
//| Typical use case for this function is real-time setting, when    |
//| you are interested in quick-and-dirty (very quick and very dirty)|
//| processing of just a few last ticks of the trend.                |
//| IMPORTANT: full scale SSA involves analysis of the ENTIRE        |
//|            dataset, with reconstruction being done for all       |
//|            positions of sliding window with subsequent           |
//|            hankelization (diagonal averaging) of the resulting   |
//|            matrix.                                               |
//| Such analysis requires O((DataLen - Window)*Window*NBasis) FLOPs |
//| and can be quite costly. However, it has nice noise - canceling  |
//| effects due to averaging.                                        |
//| This function performs REDUCED analysis of the last window. It   |
//| is much faster - just O(Window*NBasis), but its results are      |
//| DIFFERENT from that of SSAAnalyzeLast(). In particular, first few|
//| points of the trend are much more prone to noise.                |
//| INPUT PARAMETERS:                                                |
//|   S           -  SSA model                                       |
//| OUTPUT PARAMETERS:                                               |
//|   Trend       -  array[WindowSize], reconstructed trend line     |
//|   Noise       -  array[WindowSize], the rest of the signal; it   |
//|                  holds that ActualData = Trend + Noise.          |
//|   NTicks      -  current WindowSize                              |
//| CACHING / REUSE OF THE BASIS                                     |
//| Caching / reuse of previous results is performed:                |
//|   * first call performs full run of SSA; basis is stored in the  |
//|     cache                                                        |
//|   * subsequent calls reuse previously cached basis               |
//|   * if you call any function which changes model properties      |
//|     (window length, algorithm, dataset), internal basis will     |
//|     be invalidated.                                              |
//|   * the only calls which do NOT invalidate basis are listed      |
//|     below:                                                       |
//|         a) SSASetWindow() with same window length                |
//|         b) SSAAppendPointAndUpdate()                             |
//|         c) SSAAppendSequenceAndUpdate()                          |
//|         d) SSASetAlgoTopK...() with exactly same K               |
//| Calling these functions will result in reuse of previously found |
//| basis.                                                           |
//| In any case, only basis is reused. Reconstruction is performed   |
//| from scratch every time you call this function.                  |
//| HANDLING OF DEGENERATE CASES                                     |
//| Following degenerate cases may happen:                           |
//|   * dataset is empty(no analysis can be done)                    |
//|   * all sequences are shorter than the window length, no analysis|
//|     can be done                                                  |
//|   * no algorithm is specified(no analysis can be done)           |
//|   * last sequence is shorter than the window length (analysis    |
//|     can be done, but we can not perform reconstruction on the    |
//|     last sequence)                                               |
//| Calling this function in degenerate cases returns following      |
//| result:                                                          |
//|   * in any case, WindowWidth ticks is returned                   |
//|   * trend is assumed to be zero                                  |
//|   * noise is initialized by the last sequence; if last sequence  |
//|     is shorter than the window size, it is moved to the end of   |
//|     the array, and the beginning of the noise array is filled by |
//|     zeros                                                        |
//| No analysis is performed in degenerate cases (we immediately     |
//| return dummy values, no basis is constructed).                   |
//+------------------------------------------------------------------+
void CSSA::SSAAnalyzeLastWindow(CSSAModel &s,CRowDouble &trend,
                                CRowDouble &noise,int &nticks)
  {
//--- create variables
   int offs=0;
   int cnt=0;
//--- Init
   nticks=s.m_WindowWidth;
   trend.Resize(s.m_WindowWidth);
   noise.Resize(s.m_WindowWidth);
//--- Is it degenerate case?
   if(!HasSomethingToAnalyze(s) || !IsSequenceBigEnough(s,-1))
     {
      trend.Fill(0);
      noise.Fill(0);
      if(s.m_NSequences>=1)
        {
         cnt=MathMin(s.m_SequenceIdx[s.m_NSequences]-s.m_SequenceIdx[s.m_NSequences-1],nticks);
         offs=s.m_SequenceIdx[s.m_NSequences]-cnt;
         for(int i=0; i<cnt; i++)
            noise.Set(nticks-cnt+i,s.m_SequenceData[offs+i]);
        }
      return;
     }
//--- Update basis.
//--- It will take care of basis validity flags. AppendLen=0 which means
//--- that we perform initial basis evaluation.
   UpdateBasis(s,0,0.0);
//--- Perform analysis of the last window
//--- check
   if(!CAp::Assert(s.m_SequenceIdx[s.m_NSequences]-s.m_WindowWidth>=0,__FUNCTION__+": integrity check failed"))
      return;
   CApServ::RVectorSetLengthAtLeast(s.m_Tmp0,s.m_NBasis);
   CAblas::RMatrixGemVect(s.m_NBasis,s.m_WindowWidth,1.0,s.m_BasisT,0,0,0,s.m_SequenceData,s.m_SequenceIdx[s.m_NSequences]-s.m_WindowWidth,0.0,s.m_Tmp0,0);
   CAblas::RMatrixGemVect(s.m_WindowWidth,s.m_NBasis,1.0,s.m_Basis,0,0,0,s.m_Tmp0,0,0.0,trend,0);
   offs=s.m_SequenceIdx[s.m_NSequences]-s.m_WindowWidth;
   cnt=s.m_WindowWidth;
   for(int i=0; i<cnt; i++)
      noise.Set(i,s.m_SequenceData[offs+i]-trend[i]);
  }
//+------------------------------------------------------------------+
//| This function:                                                   |
//|   * builds SSA basis using internally stored(entire) dataset     |
//|   * returns reconstruction for the last NTicks of the last       |
//|     sequence                                                     |
//| If you want to analyze some other sequence, use                  |
//| SSAAnalyzeSequence().                                            |
//| Reconstruction phase involves  generation of NTicks-WindowWidth  |
//| sliding windows, their decomposition using empirical orthogonal  |
//| functions found by SSA, followed by averaging of each data point |
//| across several overlapping windows. Thus, every point in the     |
//| output trend is reconstructed using up to WindowWidth overlapping|
//| windows(WindowWidth windows exactly in the inner points, just one|
//| window at the extremal points).                                  |
//| IMPORTANT: due to averaging this function returns different      |
//|            results for different values of NTicks. It is expected|
//|            and not a bug.                                        |
//| For example:                                                     |
//|   * Trend[NTicks - 1] is always same because it is not averaged  |
//|     in any case(same applies to Trend[0]).                       |
//|   * Trend[NTicks - 2] has different values fo NTicks=WindowWidth |
//|     and NTicks=WindowWidth+1 because former case means that no   |
//|     averaging is performed, and latter case means that averaging |
//|     using two sliding windows is performed. Larger values of     |
//|     NTicks produce same results as NTicks = WindowWidth + 1.     |
//|   * ...and so on...                                              |
//| PERFORMANCE: this function has                                   |
//|         O((NTicks - WindowWidth) * WindowWidth*NBasis)           |
//| running time. If you work in time-constrained setting and have   |
//| to analyze just a few last ticks, choosing NTicks equal to       |
//| WindowWidth + SmoothingLen, with SmoothingLen = 1...WindowWidth  |
//| will result in good compromise between noise cancellation and    |
//| analysis speed.                                                  |
//| INPUT PARAMETERS:                                                |
//|   S        -  SSA model                                          |
//|   NTicks   -  number of ticks to analyze, Nticks >= 1.           |
//|            * special case of NTicks<=WindowWidth  is  handled    |
//|              by analyzing last window and returning NTicks       |
//|              last ticks.                                         |
//|            * special case NTicks>LastSequenceLen is handled by   |
//|              prepending result with NTicks-LastSequenceLen zeros.|
//| OUTPUT PARAMETERS:                                               |
//|   Trend    -  array[NTicks], reconstructed trend line            |
//|   Noise    -  array[NTicks], the rest of the signal; it holds    |
//|               that ActualData = Trend + Noise.                   |
//| CACHING / REUSE OF THE BASIS                                     |
//| Caching / reuse of previous results is performed:                |
//|      * first call performs full run of SSA; basis is stored in   |
//|        the cache                                                 |
//|      * subsequent calls reuse previously cached basis            |
//|      * if you call any function which changes model properties   |
//|        (window length, algorithm, dataset), internal basis will  |
//|        be invalidated.                                           |
//|      * the only calls which do NOT invalidate basis are listed   |
//|        below:                                                    |
//|            a) SSASetWindow() with same window length             |
//|            b) SSAAppendPointAndUpdate()                          |
//|            c) SSAAppendSequenceAndUpdate()                       |
//|            d) SSASetAlgoTopK...() with exactly same K            |
//| Calling these functions will result in reuse of previously found |
//| basis.                                                           |
//| In any case, only basis is reused. Reconstruction is performed   |
//| from scratch every time you call this function.                  |
//| HANDLING OF DEGENERATE CASES                                     |
//| Following degenerate cases may happen:                           |
//|      * dataset is empty(no analysis can be done)                 |
//|      * all sequences are shorter than the window length, no      |
//|        analysis can be done                                      |
//|      * no algorithm is specified(no analysis can be done)        |
//|      * last sequence is shorter than the window length(analysis  |
//|        can be done, but we can not perform reconstruction on the |
//|        last sequence)                                            |
//| Calling this function in degenerate cases returns following      |
//| result:                                                          |
//|      * in any case, NTicks ticks is returned                     |
//|      * trend is assumed to be zero                               |
//|      * noise is initialized by the last sequence; if last        |
//|        sequence is shorter than the window size, it is moved     |
//|        to the end of the array, and the beginning of the noise   |
//|        array is filled by zeros                                  |
//| No analysis is performed in degenerate cases(we immediately      |
//| return dummy values, no basis is constructed).                   |
//+------------------------------------------------------------------+
void CSSA::SSAAnalyzeLast(CSSAModel &s,int nticks,CRowDouble &trend,
                          CRowDouble &noise)
  {
//--- create variables
   int offs=0;
   int cnt=0;
   int cntzeros=0;
//--- check
   if(!CAp::Assert(nticks>=1,__FUNCTION__+": NTicks<1"))
      return;
//--- Init
   trend.Resize(nticks);
   noise.Resize(nticks);
//--- Is it degenerate case?
   if(!HasSomethingToAnalyze(s) || !IsSequenceBigEnough(s,-1))
     {
      trend.Fill(0);
      noise.Fill(0);
      if(s.m_NSequences>=1)
        {
         cnt=MathMin(s.m_SequenceIdx[s.m_NSequences]-s.m_SequenceIdx[s.m_NSequences-1],nticks);
         offs=s.m_SequenceIdx[s.m_NSequences]-cnt;
         for(int i=0; i<cnt; i++)
            noise.Set(nticks-cnt+i,s.m_SequenceData[offs+i]);
        }
      return;
     }
//--- Fast exit: NTicks<=WindowWidth, just last window is analyzed
   if(nticks<=s.m_WindowWidth)
     {
      SSAAnalyzeLastWindow(s,s.m_AlongTrend,s.m_AlongNoise,cnt);
      offs=s.m_WindowWidth-nticks;
      for(int i=0; i<nticks; i++)
        {
         trend.Set(i,s.m_AlongTrend[offs+i]);
         noise.Set(i,s.m_AlongNoise[offs+i]);
        }
      return;
     }
//--- Update basis.
//--- It will take care of basis validity flags. AppendLen=0 which means
//--- that we perform initial basis evaluation.
   UpdateBasis(s,0,0.0);
//--- Perform analysis:
//--- * prepend max(NTicks-LastSequenceLength,0) zeros to the beginning
//---   of array
//--- * analyze the rest with AnalyzeSequence() which assumes that we
//---   already have basis
   if(!CAp::Assert(s.m_SequenceIdx[s.m_NSequences]-s.m_SequenceIdx[s.m_NSequences-1]>=s.m_WindowWidth,__FUNCTION__+": integrity check failed / 23vd4"))
      return;
   cntzeros=MathMax(nticks-(s.m_SequenceIdx[s.m_NSequences]-s.m_SequenceIdx[s.m_NSequences-1]),0);
   for(int i=0; i<cntzeros; i++)
     {
      trend.Set(i,0.0);
      noise.Set(i,0.0);
     }
   cnt=MathMin(nticks,s.m_SequenceIdx[s.m_NSequences]-s.m_SequenceIdx[s.m_NSequences-1]);
   AnalyzeSequence(s,s.m_SequenceData,s.m_SequenceIdx[s.m_NSequences]-cnt,s.m_SequenceIdx[s.m_NSequences],trend,noise,cntzeros);
  }
//+------------------------------------------------------------------+
//| This function:                                                   |
//|      * builds SSA basis using internally stored(entire) dataset  |
//|      * returns reconstruction for the sequence being passed to   |
//|        this function                                             |
//| If you want to analyze last sequence stored in the model, use    |
//| SSAAnalyzeLast().                                                |
//| Reconstruction phase involves generation of NTicks-WindowWidth   |
//| sliding windows, their decomposition using empirical orthogonal  |
//| functions found by SSA, followed by averaging of each data point |
//| across several overlapping windows. Thus, every point in the     |
//| output trend is reconstructed using up to WindowWidth overlapping|
//| windows(WindowWidth windows exactly in the inner points, just one|
//| window at the extremal points).                                  |
//| PERFORMANCE: this function has                                   |
//|         O((NTicks - WindowWidth)*WindowWidth*NBasis)             |
//| running time. If you work in time-constrained setting and have   |
//| to analyze just a few last ticks, choosing NTicks equal to       |
//| WindowWidth + SmoothingLen, with SmoothingLen = 1...WindowWidth  |
//| will result in good compromise between noise cancellation and    |
//| analysis speed.                                                  |
//| INPUT PARAMETERS:                                                |
//|   S           -  SSA model                                       |
//|   Data        -  array[NTicks], can be larger(only NTicks leading|
//|                  elements will be used)                          |
//|   NTicks      -  number of ticks to analyze, Nticks >= 1.        |
//|               * special case of NTicks<WindowWidth is handled by |
//|                 returning zeros as trend, and signal as noise    |
//| OUTPUT PARAMETERS:                                               |
//|   Trend       -  array[NTicks], reconstructed trend line         |
//|   Noise       -  array[NTicks], the rest of the signal; it holds |
//|                  that ActualData = Trend + Noise.                |
//| CACHING / REUSE OF THE BASIS                                     |
//| Caching / reuse of previous results is performed:                |
//|      * first call performs full run of SSA; basis is stored in   |
//|        the cache                                                 |
//|      * subsequent calls reuse previously cached basis            |
//|      * if you call any function which changes model properties   |
//|        (window  length, algorithm, dataset), internal basis will |
//|        be invalidated.                                           |
//|      * the only calls which do NOT invalidate basis are listed   |
//|        below:                                                    |
//|         a) SSASetWindow() with same window length                |
//|         b) SSAAppendPointAndUpdate()                             |
//|         c) SSAAppendSequenceAndUpdate()                          |
//|         d) SSASetAlgoTopK...() with exactly same K               |
//| Calling these functions will result in reuse of previously found |
//| basis.                                                           |
//| In any case, only basis is reused. Reconstruction is performed   |
//| from scratch every time you call this function.                  |
//| HANDLING OF DEGENERATE CASES                                     |
//| Following degenerate cases may happen:                           |
//|      * dataset is empty(no analysis can be done)                 |
//|      * all sequences are shorter than the window length, no      |
//|        analysis can be done                                      |
//|      * no algorithm is specified(no analysis can be done)        |
//|      * sequence being passed is shorter than the window length   |
//| Calling this function in degenerate cases returns following      |
//| result:                                                          |
//|      * in any case, NTicks ticks is returned                     |
//|      * trend is assumed to be zero                               |
//|      * noise is initialized by the sequence.                     |
//| No analysis is performed in degenerate cases(we immediately      |
//| return dummy values, no basis is constructed).                   |
//+------------------------------------------------------------------+
void CSSA::SSAAnalyzeSequence(CSSAModel &s,CRowDouble &data,int nticks,
                              CRowDouble &trend,CRowDouble &noise)
  {
//--- check
   if(!CAp::Assert(nticks>=1,__FUNCTION__+": NTicks<1"))
      return;
   if(!CAp::Assert(data.Size()>=nticks,__FUNCTION__+": Data is too short"))
      return;
   if(!CAp::Assert(CApServ::IsFiniteVector(data,nticks),__FUNCTION__+": Data contains infinities NANs"))
      return;
//--- Init
   trend=vector<double>::Zeros(nticks);
   noise=vector<double>::Zeros(nticks);
//--- Is it degenerate case?
   if(!HasSomethingToAnalyze(s) || nticks<s.m_WindowWidth)
     {
      noise=data;
      return;
     }
//--- Update basis.
//--- It will take care of basis validity flags. AppendLen=0 which means
//--- that we perform initial basis evaluation.
   UpdateBasis(s,0,0.0);
//--- Perform analysis
   AnalyzeSequence(s,data,0,nticks,trend,noise,0);
  }
//+------------------------------------------------------------------+
//| This function builds SSA basis and performs forecasting for a    |
//| specified number of ticks, returning value of trend.             |
//| Forecast is performed as follows:                                |
//|      * SSA trend extraction is applied to last WindowWidth       |
//|        elements of the internally stored dataset; this step is   |
//|        basically a noise reduction.                              |
//|      * linear recurrence relation is applied to extracted trend  |
//| This function has following running time:                        |
//|      * O(NBasis*WindowWidth) for trend extraction phase (always  |
//|                              performed)                          |
//|      * O(WindowWidth*NTicks) for forecast phase                  |
//| NOTE: noise reduction is ALWAYS applied by this algorithm; if you|
//|       want to apply recurrence relation to raw unprocessed data, |
//|       use another function - SSAForecastSequence() which allows  |
//|       to turn on and off noise reduction phase.                  |
//| NOTE: this algorithm performs prediction using only one-last -   |
//|       sliding window. Predictions produced by such approach are  |
//|       smooth continuations of the reconstructed trend line, but  |
//|       they can be easily corrupted by noise. If you need noise - |
//|       resistant prediction, use SSAForecastAvgLast() function,   |
//|       which averages predictions built using several sliding     |
//|       windows.                                                   |
//| INPUT PARAMETERS:                                                |
//|   S        -  SSA model                                          |
//|   NTicks   -  number of ticks to forecast, NTicks >= 1           |
//| OUTPUT PARAMETERS:                                               |
//|   Trend    -  array[NTicks], predicted trend line                |
//| CACHING / REUSE OF THE BASIS                                     |
//| Caching / reuse of previous results is performed:                |
//|      * first call performs full run of SSA; basis is stored in   |
//|        the cache                                                 |
//|      * subsequent calls reuse previously cached basis            |
//|      * if you call any function which changes model properties   |
//|        (window length, algorithm, dataset), internal basis will  |
//|        be invalidated.                                           |
//|      * the only calls which do NOT invalidate basis are listed   |
//|        below:                                                    |
//|         a) SSASetWindow() with same window length                |
//|         b) SSAAppendPointAndUpdate()                             |
//|         c) SSAAppendSequenceAndUpdate()                          |
//|         d) SSASetAlgoTopK...() with exactly same K               |
//| Calling these functions will result in reuse of previously found |
//| basis.                                                           |
//| HANDLING OF DEGENERATE CASES                                     |
//| Following degenerate cases may happen:                           |
//|      * dataset is empty(no analysis can be done)                 |
//|      * all sequences are shorter than the window length, no      |
//|        analysis can be done                                      |
//|      * no algorithm is specified(no analysis can be done)        |
//|      * last sequence is shorter than the WindowWidth(analysis can|
//|         be done, but we can not perform forecasting on the last  |
//|         sequence)                                                |
//|      * window lentgh is 1(impossible to use for forecasting)     |
//|      * SSA analysis algorithm is configured to extract basis     |
//|        whose size is equal to window length(impossible to use for|
//|        forecasting; only basis whose size is less than window    |
//|        length can be used).                                      |
//| Calling this function in degenerate cases returns following      |
//| result:                                                          |
//|      * NTicks copies of the last value is returned for non-empty |
//|        task with large enough dataset, but with overcomplete     |
//|        basis (window width = 1 or basis size is equal to window  |
//|        width)                                                    |
//|      * zero trend with length = NTicks is returned for empty task|
//| No analysis is performed in degenerate cases (we immediately     |
//| return dummy values, no basis is ever constructed).              |
//+------------------------------------------------------------------+
void CSSA::SSAForecastLast(CSSAModel &s,int nticks,CRowDouble &trend)
  {
//--- create variables
   int    winw=s.m_WindowWidth;
   double v=0;
//--- check
   if(!CAp::Assert(nticks>=1,__FUNCTION__+": NTicks<1"))
      return;
//--- Init
   trend.Resize(nticks);
//--- Is it degenerate case?
   if(!HasSomethingToAnalyze(s))
     {
      trend.Fill(0);
      return;
     }
//--- check
   if(!CAp::Assert(s.m_NSequences>0,__FUNCTION__+": integrity check failed"))
      return;
   if(s.m_SequenceIdx[s.m_NSequences]-s.m_SequenceIdx[s.m_NSequences-1]<winw)
     {
      trend.Fill(0);
      return;
     }
   if(winw==1)
     {
      //--- check
      if(!CAp::Assert(s.m_NSequences>0,__FUNCTION__+": integrity check failed / 2355"))
         return;
      if(!CAp::Assert(s.m_SequenceIdx[s.m_NSequences]-s.m_SequenceIdx[s.m_NSequences-1]>0,__FUNCTION__+": integrity check failed"))
         return;
      for(int i=0; i<nticks; i++)
         trend.Set(i,s.m_SequenceData[s.m_SequenceIdx[s.m_NSequences]-1]);
      return;
     }
//--- Update basis and recurrent relation.
//--- It will take care of basis validity flags. AppendLen=0 which means
//--- that we perform initial basis evaluation.
   UpdateBasis(s,0,0.0);
//--- check
   if(!CAp::Assert(s.m_NBasis<=winw && s.m_NBasis>0,__FUNCTION__+": integrity check failed / 4f5et"))
      return;
   if(s.m_NBasis==winw)
     {
      //--- Handle degenerate situation with basis whose size
      //--- is equal to window length.
      //--- check
      if(!CAp::Assert(s.m_NSequences>0,__FUNCTION__+": integrity check failed / 2355"))
         return;
      if(!CAp::Assert(s.m_SequenceIdx[s.m_NSequences]-s.m_SequenceIdx[s.m_NSequences-1]>0,__FUNCTION__+": integrity check failed"))
         return;
      for(int i=0; i<nticks; i++)
         trend.Set(i,s.m_SequenceData[s.m_SequenceIdx[s.m_NSequences]-1]);
      return;
     }
//--- Apply recurrent formula for SSA forecasting:
//--- * first, perform smoothing of the last window
//--- * second, perform analysis phase
//--- check
   if(!CAp::Assert(s.m_NSequences>0,__FUNCTION__+": integrity check failed"))
      return;
   if(!CAp::Assert(s.m_SequenceIdx[s.m_NSequences]-s.m_SequenceIdx[s.m_NSequences-1]>=s.m_WindowWidth,__FUNCTION__+": integrity check failed"))
      return;
   s.m_Tmp0.Resize(s.m_NBasis);
   s.m_FcTrend.Resize(s.m_WindowWidth);
   CAblas::RMatrixGemVect(s.m_NBasis,s.m_WindowWidth,1.0,s.m_BasisT,0,0,0,s.m_SequenceData,s.m_SequenceIdx[s.m_NSequences]-s.m_WindowWidth,0.0,s.m_Tmp0,0);
   CAblas::RMatrixGemVect(s.m_WindowWidth,s.m_NBasis,1.0,s.m_Basis,0,0,0,s.m_Tmp0,0,0.0,s.m_FcTrend,0);
   s.m_Tmp1.Resize(winw-1);
   for(int i=1; i<winw; i++)
      s.m_Tmp1.Set(i-1,s.m_FcTrend[i]);
   for(int i=0; i<nticks; i++)
     {
      v=s.m_ForecastA[0]*s.m_Tmp1[0];
      for(int j=1; j<winw-1; j++)
        {
         v=v+s.m_ForecastA[j]*s.m_Tmp1[j];
         s.m_Tmp1.Set(j-1,s.m_Tmp1[j]);
        }
      trend.Set(i,v);
      s.m_Tmp1.Set(winw-2,v);
     }
  }
//+------------------------------------------------------------------+
//| This function builds SSA basis and performs forecasting for  a   |
//| user - specified sequence, returning value of trend.             |
//| Forecasting is done in two stages:                               |
//|      * first,  we  extract  trend from the WindowWidth last      |
//|        elements of the sequence. This stage is optional, you can |
//|        turn it off if you pass data which are already processed  |
//|        with SSA. Of course, you can turn it off even for raw     |
//|        data, but it is not recommended - noise suppression is    |
//|        very important for correct prediction.                    |
//|      * then, we apply LRR for last WindowWidth - 1 elements of   |
//|        the extracted trend.                                      |
//| This function has following running time:                        |
//|      * O(NBasis*WindowWidth)    for trend extraction phase       |
//|      * O(WindowWidth*NTicks)    for forecast phase               |
//| NOTE: this algorithm performs prediction using only one-last -   |
//|       sliding window. Predictions produced by such approach are  |
//|       smooth continuations of the reconstructed trend line, but  |
//|       they can be easily corrupted by noise. If you need noise - |
//|       resistant prediction, use SSAForecastAvgSequence()         |
//|       function, which averages predictions built using several   |
//|       sliding windows.                                           |
//| INPUT PARAMETERS:                                                |
//|   S        -  SSA model                                          |
//|   Data     -  array[NTicks], data to forecast                    |
//|   DataLen  -  number of ticks in the data, DataLen >= 1          |
//|   ForecastLen -  number of ticks to predict, ForecastLen >= 1    |
//|   ApplySmoothing - whether to apply smoothing trend extraction or|
//|               not; if you do not know what to specify, pass True.|
//| OUTPUT PARAMETERS:                                               |
//|   Trend    -  array[ForecastLen], forecasted trend               |
//| CACHING / REUSE OF THE BASIS                                     |
//| Caching / reuse of previous results is performed:                |
//|      * first call performs full run of SSA; basis is stored in   |
//|        the cache                                                 |
//|      * subsequent calls reuse previously cached basis            |
//|      * if you call any function which changes model properties   |
//|        (window length, algorithm, dataset), internal basis will  |
//|        be invalidated.                                           |
//|      * the only calls which do NOT invalidate basis are listed   |
//|        below:                                                    |
//|         a) SSASetWindow() with same window length                |
//|         b) SSAAppendPointAndUpdate()                             |
//|         c) SSAAppendSequenceAndUpdate()                          |
//|         d) SSASetAlgoTopK...() with exactly same K               |
//| Calling these functions will result in reuse of previously found |
//| basis.                                                           |
//| HANDLING OF DEGENERATE CASES                                     |
//| Following degenerate cases may happen:                           |
//|      * dataset is empty(no analysis can be done)                 |
//|      * all sequences are shorter than the window length, no      |
//|        analysis can be done                                      |
//|      * no algorithm is specified(no analysis can be done)        |
//|      * data sequence is shorter than the WindowWidth(analysis can|
//|        be done, but we can not perform forecasting on the last   |
//|        sequence)                                                 |
//|      * window lentgh is 1(impossible to use for forecasting)     |
//|      * SSA analysis algorithm is configured to extract basis     |
//|        whose size is equal to window length (impossible to use   |
//|        for forecasting; only basis whose size is less than window|
//|        length can be used).                                      |
//| Calling this function in degenerate cases returns following      |
//| result:                                                          |
//|      * ForecastLen copies of the last value is returned for      |
//|        non-empty task with large enough dataset, but with        |
//|        overcomplete basis (window width = 1 or basis size is     |
//|        equal to window width)                                    |
//|      * zero trend with length = ForecastLen is returned for empty|
//|        task                                                      |
//| No analysis is performed in degenerate cases (we immediately     |
//| return dummy values, no basis is ever constructed).              |
//+------------------------------------------------------------------+
void CSSA::SSAForecastSequence(CSSAModel &s,CRowDouble &data,
                               int datalen,int forecastlen,
                               bool applysmoothing,
                               CRowDouble &trend)
  {
//--- create variables
   int    winw=s.m_WindowWidth;
   double v=0;
//--- check
   if(!CAp::Assert(datalen>=1,__FUNCTION__+": DataLen<1"))
      return;
   if(!CAp::Assert(data.Size()>=datalen,__FUNCTION__+": Data is too short"))
      return;
   if(!CAp::Assert(CApServ::IsFiniteVector(data,datalen),__FUNCTION__+": Data contains infinities NANs"))
      return;
   if(!CAp::Assert(forecastlen>=1,__FUNCTION__+": ForecastLen<1"))
      return;
//--- Init
   trend.Resize(forecastlen);
//--- Is it degenerate case?
   if(!HasSomethingToAnalyze(s) || datalen<winw)
     {
      trend.Fill(0);
      return;
     }
   if(winw==1)
     {
      trend.Fill(data[datalen-1]);
      return;
     }
//--- Update basis.
//--- It will take care of basis validity flags. AppendLen=0 which means
//--- that we perform initial basis evaluation.
   UpdateBasis(s,0,0.0);
//--- check
   if(!CAp::Assert(s.m_NBasis<=winw && s.m_NBasis>0,__FUNCTION__+": integrity check failed / 4f5et"))
      return;
   if(s.m_NBasis==winw)
     {
      //--- Handle degenerate situation with basis whose size
      //--- is equal to window length.
      trend.Fill(data[datalen-1]);
      return;
     }
//--- Perform trend extraction
   CApServ::RVectorSetLengthAtLeast(s.m_FcTrend,s.m_WindowWidth);
   if(applysmoothing)
     {
      //--- check
      if(!CAp::Assert(datalen>=winw,__FUNCTION__+": integrity check failed"))
         return;
      CApServ::RVectorSetLengthAtLeast(s.m_Tmp0,s.m_NBasis);
      CAblas::RMatrixGemVect(s.m_NBasis,winw,1.0,s.m_BasisT,0,0,0,data,datalen-winw,0.0,s.m_Tmp0,0);
      CAblas::RMatrixGemVect(winw,s.m_NBasis,1.0,s.m_Basis,0,0,0,s.m_Tmp0,0,0.0,s.m_FcTrend,0);
     }
   else
     {
      for(int i=0; i<winw; i++)
         s.m_FcTrend.Set(i,data[datalen+i-winw]);
     }
//--- Apply recurrent formula for SSA forecasting
   CApServ::RVectorSetLengthAtLeast(s.m_Tmp1,winw-1);
   for(int i=1; i<winw; i++)
      s.m_Tmp1.Set(i-1,s.m_FcTrend[i]);
   for(int i=0; i<forecastlen; i++)
     {
      v=s.m_ForecastA[0]*s.m_Tmp1[0];
      for(int j=1; j<=winw-2; j++)
        {
         v=v+s.m_ForecastA[j]*s.m_Tmp1[j];
         s.m_Tmp1.Set(j-1,s.m_Tmp1[j]);
        }
      trend.Set(i,v);
      s.m_Tmp1.Set(winw-2,v);
     }
  }
//+------------------------------------------------------------------+
//| This function builds SSA basis and performs forecasting for a    |
//| specified number of ticks, returning value of trend.             |
//| Forecast is performed as follows:                                |
//|      * SSA  trend extraction is applied to last M sliding windows|
//|        of the internally stored dataset                          |
//|      * for each of M sliding windows, M predictions are built    |
//|      * average value of M predictions is returned                |
//| This function has following running time:                        |
//|      * O(NBasis*WindowWidth*M)  for trend extraction phase       |
//|                                 (always performed)               |
//|      * O(WindowWidth*NTicks*M)  for forecast phase               |
//| NOTE: noise reduction is ALWAYS applied by this algorithm; if you|
//|       want to apply recurrence relation to raw unprocessed data, |
//|       use another function - SSAForecastSequence() which allows  |
//|       to turn on and off noise reduction phase.                  |
//| NOTE: combination of several predictions results in lesser       |
//|       sensitivity to noise, but it may produce undesirable       |
//|       discontinuities between last point of the trend and first  |
//|       point of the prediction. The reason is that last point of  |
//|       the trend is usually corrupted by noise, but average value |
//|       of several predictions is less sensitive to noise, thus    |
//|       discontinuity appears. It is not a bug.                    |
//| INPUT PARAMETERS:                                                |
//|   S        -  SSA model                                          |
//|   M        -  number of sliding windows to combine, M >= 1. If   |
//|               your dataset has less than M sliding windows, this |
//|               parameter will be silently reduced.                |
//|   NTicks   -  number of ticks to forecast, NTicks >= 1           |
//| OUTPUT PARAMETERS:                                               |
//|   Trend    -  array[NTicks], predicted trend line                |
//| CACHING / REUSE OF THE BASIS                                     |
//| Caching / reuse of previous results is performed:                |
//|      * first call performs full run of SSA; basis is stored in   |
//|        the cache                                                 |
//|      * subsequent calls reuse previously cached basis            |
//|      * if you call any function which changes model properties   |
//|        (window length, algorithm, dataset), internal basis will  |
//|        be invalidated.                                           |
//|      * the only calls which do NOT invalidate basis are listed   |
//|        below:                                                    |
//|            a) SSASetWindow() with same window length             |
//|            b) SSAAppendPointAndUpdate()                          |
//|            c) SSAAppendSequenceAndUpdate()                       |
//|            d) SSASetAlgoTopK...() with exactly same K            |
//| Calling these functions will result in reuse of previously found |
//| basis.                                                           |
//| HANDLING OF DEGENERATE CASES                                     |
//| Following degenerate cases may happen:                           |
//|      * dataset is empty(no analysis can be done)                 |
//|      * all sequences are shorter than the window length, no      |
//|        analysis can be done                                      |
//|      * no algorithm is specified(no analysis can be done)        |
//|      * last sequence is shorter than the WindowWidth(analysis can|
//|        be done, but we can not perform forecasting on the last   |
//|        sequence)                                                 |
//|      * window lentgh is 1(impossible to use for forecasting)     |
//|      * SSA analysis algorithm is configured to extract basis     |
//|        whose size is equal to window length(impossible to use for|
//|        forecasting; only basis whose size is less than window    |
//|        length can be used).                                      |
//| Calling this function in degenerate cases returns following      |
//| result:                                                          |
//|   * NTicks copies of the last value is returned for non-empty    |
//|     task with large enough dataset, but with overcomplete basis  |
//|     (window width = 1 or basis size is equal to window width)    |
//|   * zero trend with length = NTicks is returned for empty task   |
//| No analysis is performed in degenerate cases (we immediately     |
//| return dummy values, no basis is ever constructed).              |
//+------------------------------------------------------------------+
void CSSA::SSAForecastAvgLast(CSSAModel &s,int m,int nticks,
                              CRowDouble &trend)
  {
   int winw=s.m_WindowWidth;
//--- check
   if(!CAp::Assert(nticks>=1,__FUNCTION__+": NTicks<1"))
      return;
   if(!CAp::Assert(m>=1,__FUNCTION__+": M<1"))
      return;
//--- Init
   trend.Resize(nticks);
//--- Is it degenerate case?
   if(!HasSomethingToAnalyze(s))
     {
      trend.Fill(0);
      return;
     }
//--- check
   if(!CAp::Assert(s.m_NSequences>0,__FUNCTION__+": integrity check failed"))
      return;
   if(s.m_SequenceIdx[s.m_NSequences]-s.m_SequenceIdx[s.m_NSequences-1]<winw)
     {
      trend.Fill(0);
      return;
     }
   if(winw==1)
     {
      //--- check
      if(!CAp::Assert(s.m_NSequences>0,__FUNCTION__+": integrity check failed / 2355"))
         return;
      if(!CAp::Assert(s.m_SequenceIdx[s.m_NSequences]-s.m_SequenceIdx[s.m_NSequences-1]>0,__FUNCTION__+": integrity check failed"))
         return;
      trend.Fill(s.m_SequenceData[s.m_SequenceIdx[s.m_NSequences]-1]);
      return;
     }
//--- Update basis and recurrent relation.
//--- It will take care of basis validity flags. AppendLen=0 which means
//--- that we perform initial basis evaluation.
   UpdateBasis(s,0,0.0);
//--- check
   if(!CAp::Assert(s.m_NBasis<=winw && s.m_NBasis>0,__FUNCTION__+": integrity check failed / 4f5et"))
      return;
   if(s.m_NBasis==winw)
     {
      //--- Handle degenerate situation with basis whose size
      //--- is equal to window length.
      //--- check
      if(!CAp::Assert(s.m_NSequences>0,__FUNCTION__+": integrity check failed / 2355"))
         return;
      if(!CAp::Assert(s.m_SequenceIdx[s.m_NSequences]-s.m_SequenceIdx[s.m_NSequences-1]>0,__FUNCTION__+": integrity check failed"))
         return;
      trend.Fill(s.m_SequenceData[s.m_SequenceIdx[s.m_NSequences]-1]);
      return;
     }
//--- Decrease M if we have less than M sliding windows.
//--- Forecast.
   m=MathMin(m,s.m_SequenceIdx[s.m_NSequences]-s.m_SequenceIdx[s.m_NSequences-1]-winw+1);
   if(!CAp::Assert(m>=1,__FUNCTION__+": integrity check failed"))
      return;
   ForecastAvgSequence(s,s.m_SequenceData,s.m_SequenceIdx[s.m_NSequences-1],s.m_SequenceIdx[s.m_NSequences],m,nticks,true,trend,0);
  }
//+------------------------------------------------------------------+
//| This function builds SSA basis and performs forecasting for a    |
//| user - specified sequence, returning value of trend.             |
//| Forecasting is done in two stages:                               |
//|   * first, we extract trend from M last sliding windows of the   |
//|     sequence. This stage is optional, you can turn it off i you  |
//|     pass data which are already processed with SSA. Of course,   |
//|     you can turn it off even for raw data, but it is not         |
//|     recommended - noise suppression is very important for correct|
//|     prediction.                                                  |
//|   * then, we apply LRR independently for M sliding windows       |
//|   * average of M predictions is returned                         |
//| This function has following running time:                        |
//|   * O(NBasis*WindowWidth*M)     for trend extraction phase       |
//|   * O(WindowWidth*NTicks*M)     for forecast phase               |
//| NOTE: combination of several predictions results in lesser       |
//|       sensitivity to noise, but it may produce undesirable       |
//|       discontinuities between last point of the trend and first  |
//|       point of the prediction. The reason is that last point of  |
//|       the trend is usually corrupted by noise, but average value |
//|       of several predictions is less sensitive to noise, thus    |
//|       discontinuity appears. It is not a bug.                    |
//| INPUT PARAMETERS:                                                |
//|   S           -  SSA model                                       |
//|   Data        -  array[NTicks], data to forecast                 |
//|   DataLen     -  number of ticks in the data, DataLen >= 1       |
//|   M           -  number of sliding windows to combine, M >= 1.   |
//|                  If your dataset has less than M sliding windows,|
//|                  this parameter will be silently reduced.        |
//|   ForecastLen -  number of ticks to predict, ForecastLen >= 1    |
//|   ApplySmoothing - whether to apply smoothing trend extraction   |
//|                  or not. if you do not know what to specify,     |
//|                  pass true.                                      |
//| OUTPUT PARAMETERS:                                               |
//|   Trend       -  array[ForecastLen], forecasted trend            |
//| CACHING / REUSE OF THE BASIS                                     |
//| Caching / reuse of previous results is performed:                |
//|   * first call performs full run of SSA; basis is stored in      |
//|     the cache                                                    |
//|   * subsequent calls reuse previously cached basis               |
//|   * if you call any function which changes model properties      |
//|     (window length, algorithm, dataset), internal basis will be  |
//|     invalidated.                                                 |
//|   * the only calls which do NOT invalidate basis are listed      |
//|     below:                                                       |
//|      a) SSASetWindow() with same window length                   |
//|      b) SSAAppendPointAndUpdate()                                |
//|      c) SSAAppendSequenceAndUpdate()                             |
//|      d) SSASetAlgoTopK...() with exactly same K                  |
//| Calling these functions will result in reuse of previously found |
//| basis.                                                           |
//| HANDLING OF DEGENERATE CASES                                     |
//| Following degenerate cases may happen:                           |
//|   * dataset is empty(no analysis can be done)                    |
//|   * all sequences are shorter than the window length, no analysis|
//|     can be done                                                  |
//|   * no algorithm is specified(no analysis can be done)           |
//|   * data sequence is shorter than the WindowWidth (analysis can  |
//|     be done, but we can not perform forecasting on the last      |
//|     sequence)                                                    |
//|   * window lentgh is 1 (impossible to use for forecasting)       |
//|   * SSA analysis algorithm is configured to extract basis whose  |
//|     size is equal to window length (impossible to use for        |
//|     forecasting; only basis whose size is less than window length|
//|     can be used).                                                |
//| Calling this function in degenerate cases returns following      |
//| result:                                                          |
//|   * ForecastLen copies of the last value is returned for         |
//|     non-empty task with large enough dataset, but with           |
//|     overcomplete basis (window width = 1 or basis size           |
//|     is equal to window width)                                    |
//|   * zero trend with length = ForecastLen is returned for         |
//|     empty task                                                   |
//| No analysis is performed in degenerate case s(we immediately     |
//| return dummy values, no basis is ever constructed).              |
//+------------------------------------------------------------------+
void CSSA::SSAForecastAvgSequence(CSSAModel &s,CRowDouble &data,
                                  int datalen,int m,
                                  int forecastlen,bool applysmoothing,
                                  CRowDouble &trend)
  {
   int winw=s.m_WindowWidth;
//--- check
   if(!CAp::Assert(datalen>=1,__FUNCTION__+": DataLen<1"))
      return;
   if(!CAp::Assert(m>=1,__FUNCTION__+": M<1"))
      return;
   if(!CAp::Assert(data.Size()>=datalen,__FUNCTION__+": Data is too short"))
      return;
   if(!CAp::Assert(CApServ::IsFiniteVector(data,datalen),__FUNCTION__+": Data contains infinities NANs"))
      return;
   if(!CAp::Assert(forecastlen>=1,__FUNCTION__+": ForecastLen<1"))
      return;
//--- Init
   trend.Resize(forecastlen);
//--- Is it degenerate case?
   if(!HasSomethingToAnalyze(s) || datalen<winw)
     {
      trend.Fill(0);
      return;
     }
   if(winw==1)
     {
      trend.Fill(data[datalen-1]);
      return;
     }
//--- Update basis.
//--- It will take care of basis validity flags. AppendLen=0 which means
//--- that we perform initial basis evaluation.
   UpdateBasis(s,0,0.0);
//--- check
   if(!CAp::Assert(s.m_NBasis<=winw && s.m_NBasis>0,__FUNCTION__+": integrity check failed / 4f5et"))
      return;
   if(s.m_NBasis==winw)
     {
      //--- Handle degenerate situation with basis whose size
      //--- is equal to window length.
      trend.Fill(data[datalen-1]);
      return;
     }
//--- Decrease M if we have less than M sliding windows.
//--- Forecast.
   m=MathMin(m,datalen-winw+1);
//--- check
   if(!CAp::Assert(m>=1,__FUNCTION__+": integrity check failed"))
      return;
   ForecastAvgSequence(s,data,0,datalen,m,forecastlen,applysmoothing,trend,0);
  }
//+------------------------------------------------------------------+
//| This function evaluates current model and tells whether we have  |
//| some data which can be analyzed by current algorithm, or not.    |
//| No analysis can be done in the following degenerate cases:       |
//|   * dataset is empty                                             |
//|   * all sequences are shorter than the window length             |
//|   * no algorithm is specified                                    |
//+------------------------------------------------------------------+
bool CSSA::HasSomethingToAnalyze(CSSAModel &s)
  {
//--- create variables
   bool result;
   bool allsmaller=true;
   bool isdegenerate=false;
//--- initialization
   isdegenerate=isdegenerate || s.m_AlgoType==0;
   isdegenerate=isdegenerate || s.m_NSequences==0;
//---
   for(int i=0; i<s.m_NSequences; i++)
      allsmaller=allsmaller && s.m_SequenceIdx[i+1]-s.m_SequenceIdx[i]<s.m_WindowWidth;
   isdegenerate=isdegenerate || allsmaller;
   result=!isdegenerate;
//--- return result
   return(result);
  }
//+------------------------------------------------------------------+
//| This function checks whether I-th sequence is big enough for     |
//| analysis or not.                                                 |
//| I = -1 is used to denote last sequence(for NSequences = 0)       |
//+------------------------------------------------------------------+
bool CSSA::IsSequenceBigEnough(CSSAModel &s,int i)
  {
//--- check
   if(!CAp::Assert(i>=-1 && i<s.m_NSequences))
      return(false);
   if(s.m_NSequences==0)
      return(false);
//---
   if(i<0)
      i=s.m_NSequences-1;
//--- return result
   return (s.m_SequenceIdx[i+1]-s.m_SequenceIdx[i]>=s.m_WindowWidth);
  }
//+------------------------------------------------------------------+
//| This function performs basis update. Either full update          |
//| (recalculated from the very beginning) or partial update         |
//| (handles append to the end of the dataset).                      |
//| With AppendLen = 0 this function behaves as follows:             |
//|   * if AreBasisAndSolverValid = False, then solver object is     |
//|     created from scratch, initial calculations are performed     |
//|     according to specific SSA algorithm being chosen. Basis /    |
//|     Solver validity flag is set to True, then we immediately     |
//|     return.                                                      |
//|   * if AreBasisAndSolverValid = True, then nothing is done - we  |
//|     immediately return.                                          |
//| With AppendLen > 0 this function behaves as follows:             |
//|   * if AreBasisAndSolverValid = False, then exception is         |
//|     generated; you can append points only to fully constructed   |
//|     basis. Call this function with zero AppendLen BEFORE append, |
//|     then perform append, then call it one more time with non-zero|
//|     AppendLen.                                                   |
//|   * if AreBasisAndSolverValid = True, then basis is incrementally|
//|     updated. It also updates recurrence relation used for        |
//|     prediction. It is expected that either AppendLen = 1, or     |
//|     AppendLen = length (last_sequence). Basis update  is         |
//|     performed with probability UpdateIts (larger-than-one values |
//|     mean that some amount of iterations is always performed).    |
//| In any case, after calling this function we either:              |
//|   * have an exception                                            |
//|   * have completely valid basis                                  |
//| IMPORTANT: this function expects that we do NOT call it for      |
//|            degenerate tasks (no data). So, call it after check   |
//|            with HasSomethingToAnalyze() returned True.           |
//+------------------------------------------------------------------+
void CSSA::UpdateBasis(CSSAModel &s,int appendlen,double updateits)
  {
//--- create variables
   int    i=0;
   int    j=0;
   int    k=0;
   int    srcoffs=0;
   int    dstoffs=0;
   int    winw=s.m_WindowWidth;
   int    windowstotal=0;
   int    requesttype=0;
   int    requestsize=0;
   double v=0;
   bool   degeneraterecurrence=false;
   double nu2=0;
   int    subspaceits=0;
   bool   needevd=false;
//--- Critical checks
   if(!CAp::Assert(appendlen>=0,__FUNCTION__+": incorrect parameters passed to UpdateBasis(),integrity check failed"))
      return;
   if(!CAp::Assert(!(!s.m_AreBasisAndSolverValid && appendlen!=0),__FUNCTION__+": incorrect parameters passed to UpdateBasis(),integrity check failed"))
      return;
   if(!CAp::Assert(!(appendlen==0 && updateits>0.0),__FUNCTION__+": incorrect parameters passed to UpdateBasis(),integrity check failed"))
      return;
//--- Everything is OK, nothing to do
   if(s.m_AreBasisAndSolverValid && appendlen==0)
      return;
//--- Seed RNG with fixed or random seed.
//--- RNG used when pseudorandomly deciding whether
//--- to re-evaluate basis or not. Sandom seed is
//--- important when we have several simultaneously
//--- calculated SSA models - we do not want them
//--- to be re-evaluated in same moments).
   if(!s.m_AreBasisAndSolverValid)
     {
      if(s.m_RngSeed>0)
         CHighQualityRand::HQRndSeed(s.m_RngSeed,s.m_RngSeed+235,s.m_RS);
      else
         CHighQualityRand::HQRndRandomize(s.m_RS);
     }
//--- Compute XXT for algorithms which need it
   if(!s.m_AreBasisAndSolverValid)
     {
      //--- check
      if(!CAp::Assert(appendlen==0,__FUNCTION__+": integrity check failed / 34cx6"))
         return;
      switch(s.m_AlgoType)
        {
         case 2:
            //--- Compute X*X^T for direct algorithm.
            //--- Quite straightforward, no subtle optimizations.
            s.m_XXT=matrix<double>::Zeros(winw,winw);
            windowstotal=0;
            for(i=0; i<s.m_NSequences; i++)
               windowstotal+=MathMax(s.m_SequenceIdx[i+1]-s.m_SequenceIdx[i]-winw+1,0);
            //--- check
            if(!CAp::Assert(windowstotal>0,__FUNCTION__+": integrity check in UpdateBasis() failed / 76t34"))
               return;
            UpdateXXTPrepare(s,windowstotal,winw,s.m_MemoryLimit);
            for(i=0; i<s.m_NSequences; i++)
              {
               for(j=0; j<MathMax(s.m_SequenceIdx[i+1]-s.m_SequenceIdx[i]-winw+1,0); j++)
                  UpdateXXTSend(s,s.m_SequenceData,s.m_SequenceIdx[i]+j,s.m_XXT);
              }
            UpdateXXTFinalize(s,s.m_XXT);
            break;
         case 3:
            //--- Compute X*X^T for real-time algorithm:
            //--- * prepare queue of windows to merge into XXT
            //--- * shuffle queue in order to avoid time-related biases in algorithm
            //--- * dequeue first chunk
            s.m_XXT.Resize(winw,winw);
            windowstotal=0;
            for(i=0; i<s.m_NSequences; i++)
               windowstotal+=MathMax(s.m_SequenceIdx[i+1]-s.m_SequenceIdx[i]-winw+1,0);
            //--- check
            if(!CAp::Assert(windowstotal>0,__FUNCTION__+": integrity check in UpdateBasis() failed / 76t34"))
               return;
            CApServ::IVectorSetLengthAtLeast(s.m_Rtqueue,windowstotal);
            dstoffs=0;
            for(i=0; i<s.m_NSequences; i++)
               for(j=0; j<MathMax(s.m_SequenceIdx[i+1]-s.m_SequenceIdx[i]-winw+1,0); j++)
                 {
                  srcoffs=s.m_SequenceIdx[i]+j;
                  s.m_Rtqueue.Set(dstoffs,srcoffs);
                  dstoffs++;
                 }
            //--- check
            if(!CAp::Assert(dstoffs==windowstotal,__FUNCTION__+": integrity check in UpdateBasis() failed / fh45f"))
               return;
            if(s.m_RtPowerUp>1)
               //--- Shuffle queue, it helps to avoid time-related bias in algorithm
               for(i=0; i<windowstotal; i++)
                 {
                  j=i+CHighQualityRand::HQRndUniformI(s.m_RS,windowstotal-i);
                  s.m_Rtqueue.Swap(i,j);
                 }
            s.m_RtqueueCnt=windowstotal;
            s.m_RtqueueChunk=1;
            s.m_RtqueueChunk=MathMax(s.m_RtqueueChunk,s.m_RtqueueCnt/s.m_RtPowerUp);
            s.m_RtqueueChunk=MathMax(s.m_RtqueueChunk,2*s.m_TopK);
            RealtimeDequeue(s,0.0,MathMin(s.m_RtqueueChunk,s.m_RtqueueCnt));
            break;
        }
     }
//--- Handle possible updates for XXT:
//--- * check that append involves either last point of last sequence,
//---   or entire last sequence
//--- * if last sequence is shorter than window width, perform quick exit -
//---   we have nothing to update - no windows to insert into XXT
//--- * update XXT
   if(appendlen>0)
     {
      //--- check
      if(!CAp::Assert(s.m_AreBasisAndSolverValid,__FUNCTION__+": integrity check failed / 5gvz3"))
         return;
      if(!CAp::Assert(s.m_NSequences>=1,__FUNCTION__+": integrity check failed / 658ev"))
         return;
      if(!CAp::Assert(appendlen==1 || appendlen==s.m_SequenceIdx[s.m_NSequences]-s.m_SequenceIdx[s.m_NSequences-1]-winw+1,__FUNCTION__+": integrity check failed / sd3g7"))
         return;
      if(s.m_SequenceIdx[s.m_NSequences]-s.m_SequenceIdx[s.m_NSequences-1]<winw)
         //--- Last sequence is too short, nothing to update
         return;
      if(s.m_AlgoType==2 || s.m_AlgoType==3)
        {
         if(appendlen>1)
           {
            //--- Long append, use GEMM for updates
            UpdateXXTPrepare(s,appendlen,winw,s.m_MemoryLimit);
            for(j=0; j<MathMax(s.m_SequenceIdx[s.m_NSequences]-s.m_SequenceIdx[s.m_NSequences-1]-winw+1,0); j++)
               UpdateXXTSend(s,s.m_SequenceData,s.m_SequenceIdx[s.m_NSequences-1]+j,s.m_XXT);
            UpdateXXTFinalize(s,s.m_XXT);
           }
         else
           {
            //--- Just one element is added, use rank-1 update
            CAblas::RMatrixGer(winw,winw,s.m_XXT,0,0,1.0,s.m_SequenceData,s.m_SequenceIdx[s.m_NSequences]-winw,s.m_SequenceData,s.m_SequenceIdx[s.m_NSequences]-winw);
           }
        }
     }
//--- Now, perform basis calculation - either full recalculation (AppendLen=0)
//--- or quick update (AppendLen>0).
   switch(s.m_AlgoType)
     {
      case 1:
         //--- Precomputed basis
         if(winw!=s.m_PrecomputedWidth)
           {
            //--- Window width has changed, reset basis to zeros
            s.m_NBasis=1;
            s.m_Basis-matrix<double>::Zeros(winw,1);
            s.m_SV=vector<double>::Zeros(1);
           }
         else
           {
            //--- OK, use precomputed basis
            s.m_NBasis=s.m_PrecomputedNBasis;
            s.m_Basis=s.m_PrecomputedBasis;
            s.m_SV=vector<double>::Zeros(s.m_NBasis);
           }
         s.m_BasisT=s.m_Basis.Transpose()+0;
         break;
      case 2:
         //--- Direct top-K algorithm
         //--- Calculate eigenvectors with SMatrixEVD(), reorder by descending
         //--- of magnitudes.
         //--- Update is performed for invalid basis or for non-zero UpdateIts.
         needevd=!s.m_AreBasisAndSolverValid;
         needevd=needevd || updateits>=1.0;
         needevd=needevd || CHighQualityRand::HQRndUniformR(s.m_RS)<(double)(updateits-(int)MathFloor(updateits));
         if(needevd)
           {
            s.m_DbgCntEVD++;
            s.m_NBasis=MathMin(winw,s.m_TopK);
            if(!CEigenVDetect::SMatrixEVD(s.m_XXT,winw,1,true,s.m_SV,s.m_Basis))
              {
               CAp::Assert(false,__FUNCTION__+": SMatrixEVD failed");
               return;
              }
            for(i=0; i<winw; i++)
              {
               k=winw-1-i;
               if(i>=k)
                  break;
               s.m_SV.Swap(i,k);
               s.m_Basis.SwapCols(i,k);
              }
            for(i=0; i<=s.m_NBasis-1; i++)
               s.m_SV.Set(i,MathSqrt(MathMax(s.m_SV[i],0.0)));
            s.m_BasisT=s.m_Basis.Transpose()+0;
           }
         break;
      case 3:
         //--- Real-time top-K.
         //--- Determine actual number of basis components, prepare subspace
         //--- solver (either create from scratch or reuse).
         //--- Update is always performed for invalid basis; for a valid basis
         //--- it is performed with probability UpdateIts.
         if(s.m_RtPowerUp==1)
            subspaceits=s.m_DefaultSubspaceits;
         else
            subspaceits=3;
         if(appendlen>0)
           {
            //--- check
            if(!CAp::Assert(s.m_AreBasisAndSolverValid,__FUNCTION__+": integrity check in UpdateBasis() failed / srg6f"))
               return;
            if(!CAp::Assert(updateits>=0.0,__FUNCTION__+": integrity check in UpdateBasis() failed / srg4f"))
               return;
            subspaceits=(int)MathFloor(updateits);
            if(CHighQualityRand::HQRndUniformR(s.m_RS)<(double)(updateits-(int)MathFloor(updateits)))
               subspaceits++;
            //--- check
            if(!CAp::Assert(subspaceits>=0,__FUNCTION__+": integrity check in UpdateBasis() failed / srg9f"))
               return;
            //--- Dequeue pending dataset and merge it into XXT.
            //--- Dequeuing is done only for appends, and only when we have
            //--- non-empty queue.
            if(s.m_RtqueueCnt>0)
               RealtimeDequeue(s,1.0,MathMin(s.m_RtqueueChunk,s.m_RtqueueCnt));
           }
         //--- Now, proceed to solver
         if(subspaceits>0)
           {
            if(appendlen==0)
              {
               s.m_NBasis=MathMin(winw,s.m_TopK);
               CEigenVDetect::EigSubSpaceCreateBuf(winw,s.m_NBasis,s.m_Solver);
              }
            else
               CEigenVDetect::EigSubSpaceSetWarmStart(s.m_Solver,true);
            CEigenVDetect::EigSubSpaceSetCond(s.m_Solver,0.0,subspaceits);
            //--- Perform initial basis estimation
            s.m_DbgCntEVD++;
            CEigenVDetect::EigSubSpaceOOCStart(s.m_Solver,0);
            while(CEigenVDetect::EigSubSpaceOOCContinue(s.m_Solver))
              {
               CEigenVDetect::EigSubSpaceOOCGetRequestInfo(s.m_Solver,requesttype,requestsize);
               //--- check
               if(!CAp::Assert(requesttype==0,__FUNCTION__+": integrity check in UpdateBasis() failed / 346372"))
                  return;
               CAblas::RMatrixGemm(winw,requestsize,winw,1.0,s.m_XXT,0,0,0,s.m_Solver.m_X,0,0,0,0.0,s.m_Solver.m_AX,0,0);
              }
            CEigenVDetect::EigSubSpaceOOCStop(s.m_Solver,s.m_SV,s.m_Basis,s.m_SolverRep);
            s.m_SV.Clip(0,DBL_MAX);
            s.m_SV=s.m_SV.Sqrt()+0;
            s.m_BasisT=s.m_Basis.Transpose()+0;
           }
         break;
      default:
         CAp::Assert(false,__FUNCTION__+": integrity check in UpdateBasis() failed / dfgs34");
         break;
     }
//--- Update recurrent relation
   CApServ::RVectorSetLengthAtLeast(s.m_ForecastA,MathMax(winw-1,1));
   degeneraterecurrence=false;
   if(winw>1)
     {
      //--- Non-degenerate case
      s.m_Tmp0=s.m_BasisT.Col(winw-1)+0;
      s.m_Tmp0.Resize(s.m_NBasis);
      nu2=(s.m_Tmp0.Pow(2.0)+0).Sum();
      if(nu2<(1.0-1000*CMath::m_machineepsilon))
         CAblas::RMatrixGemVect(winw-1,s.m_NBasis,1/(1-nu2),s.m_BasisT,0,0,1,s.m_Tmp0,0,0.0,s.m_ForecastA,0);
      else
         degeneraterecurrence=true;
     }
   else
      degeneraterecurrence=true;
   if(degeneraterecurrence)
     {
      s.m_ForecastA.Fill(0.0);
      s.m_ForecastA.Set(MathMax(winw-2,0),1.0);
     }
//--- Set validity flag
   s.m_AreBasisAndSolverValid=true;
  }
//+------------------------------------------------------------------+
//| This function performs analysis using current basis. It assumes  |
//| and checks that validity flag AreBasisAndSolverValid is set.     |
//| INPUT PARAMETERS:                                                |
//|   S        -  model                                              |
//|   Data     -  array which holds data in elements [I0, I1):       |
//|               * right bound is not included.                     |
//|               * I1 - I0 >= WindowWidth(assertion is performed).  |
//|   Trend    -  preallocated output array, large enough            |
//|   Noise    -  preallocated output array, large enough            |
//|   Offs     -  offset in Trend / Noise where result is stored;    |
//|               I1 - I0 elements are written starting at offset    |
//|               Offs.                                              |
//| OUTPUT PARAMETERS:                                               |
//|   Trend, Noise - processing results                              |
//+------------------------------------------------------------------+
void CSSA::AnalyzeSequence(CSSAModel &s,CRowDouble &data,
                           int i0,int i1,
                           CRowDouble &trend,
                           CRowDouble &noise,
                           int offs)
  {
//--- create variables
   int winw=0;
   int nwindows=0;
   int cnt=0;
   int batchstart=0;
   int batchlimit=0;
   int batchsize=0;
//--- check
   if(!CAp::Assert(s.m_AreBasisAndSolverValid,__FUNCTION__+": integrity check failed / d84sz0"))
      return;
   if(!CAp::Assert(i1-i0>=s.m_WindowWidth,__FUNCTION__+": integrity check failed / d84sz1"))
      return;
   if(!CAp::Assert(s.m_NBasis>=1,__FUNCTION__+": integrity check failed / d84sz2"))
      return;
//--- initialization
   nwindows=i1-i0-s.m_WindowWidth+1;
   winw=s.m_WindowWidth;
   batchlimit=MathMax(nwindows,1);
   if(s.m_MemoryLimit>0)
      batchlimit=MathMin(batchlimit,MathMax(s.m_MemoryLimit/winw,4*winw));
//--- Zero-initialize trend and counts
   cnt=i1-i0;
   CApServ::IVectorSetLengthAtLeast(s.m_AseqCounts,cnt);
   s.m_AseqCounts.Fill(0,0,cnt);
   for(int i=0; i<cnt; i++)
      trend.Set(offs+i,0.0);
//--- Reset temporaries if algorithm settings changed since last round
   if(s.m_AseqTrajectory.Cols()!=winw)
      s.m_AseqTrajectory.Resize(0,0);
   if(s.m_AseqTbProduct.Cols()!=s.m_NBasis)
      s.m_AseqTbProduct.Resize(0,0);
//--- Perform batch processing
   CApServ::RMatrixSetLengthAtLeast(s.m_AseqTrajectory,batchlimit,winw);
   CApServ::RMatrixSetLengthAtLeast(s.m_AseqTbProduct,batchlimit,s.m_NBasis);
   batchsize=0;
   batchstart=offs;
   for(int i=0; i<nwindows; i++)
     {
      //--- Enqueue next row of trajectory matrix
      if(batchsize==0)
         batchstart=i;
      for(int j=0; j<winw; j++)
         s.m_AseqTrajectory.Set(batchsize,j,data[i0+i+j]);
      batchsize++;
      //--- Process batch
      if(batchsize==batchlimit || i==nwindows-1)
        {
         //--- Project onto basis
         CAblas::RMatrixGemm(batchsize,s.m_NBasis,winw,1.0,s.m_AseqTrajectory,0,0,0,s.m_BasisT,0,0,1,0.0,s.m_AseqTbProduct,0,0);
         CAblas::RMatrixGemm(batchsize,winw,s.m_NBasis,1.0,s.m_AseqTbProduct,0,0,0,s.m_BasisT,0,0,0,0.0,s.m_AseqTrajectory,0,0);
         //--- Hankelize
         for(int k=0; k<batchsize; k++)
            for(int j=0; j<winw; j++)
              {
               trend.Add(offs+batchstart+k+j,s.m_AseqTrajectory.Get(k,j));
               s.m_AseqCounts.Set(batchstart+k+j,s.m_AseqCounts[batchstart+k+j]+1);
              }
         //--- Reset batch size
         batchsize=0;
        }
     }
   for(int i=0; i<cnt; i++)
     {
      trend.Mul(offs+i,1.0/s.m_AseqCounts[i]);
      noise.Set(offs+i,data[i0+i]-trend[offs+i]);
     }
  }
//+------------------------------------------------------------------+
//| This function performs averaged forecasting. It assumes that     |
//| basis is already built, everything is valid and checked. See     |
//| comments on similar public functions to find out more about      |
//| averaged predictions.                                            |
//| INPUT PARAMETERS:                                                |
//|   S           -  model                                           |
//|   Data        -  array which holds data in elements [I0, I1):    |
//|                  * right bound is not included.                  |
//|                  * I1 - I0 >= WindowWidth(assertion is performed)|
//|   M           -  number of sliding windows to combine, M >= 1.   |
//|                  If your dataset has less than M sliding windows,|
//|                  this parameter will be silently reduced.        |
//|   ForecastLen -  number of ticks to predict, ForecastLen >= 1    |
//|   Trend       -  preallocated output array, large enough         |
//|   Offs        -  offset in Trend where result is stored;         |
//|                  I1 - I0 elements are written starting at offset |
//|                  Offs.                                           |
//| OUTPUT PARAMETERS:                                               |
//|   Trend       -  array[ForecastLen], forecasted trend            |
//+------------------------------------------------------------------+
void CSSA::ForecastAvgSequence(CSSAModel &s,CRowDouble &data,
                               int i0,int i1,int m,
                               int forecastlen,
                               bool smooth,
                               CRowDouble &trend,
                               int offs)
  {
   int winw=s.m_WindowWidth;
//--- check
   if(!CAp::Assert(s.m_AreBasisAndSolverValid,__FUNCTION__+": integrity check failed / d84sz0"))
      return;
   if(!CAp::Assert(i1-i0-s.m_WindowWidth+1>=m,__FUNCTION__+": integrity check failed / d84sz1"))
      return;
   if(!CAp::Assert(s.m_NBasis>=1,__FUNCTION__+": integrity check failed / d84sz2"))
      return;
   if(!CAp::Assert(s.m_WindowWidth>=2,__FUNCTION__+": integrity check failed / 5tgdg5"))
      return;
   if(!CAp::Assert(s.m_WindowWidth>s.m_NBasis,__FUNCTION__+": integrity check failed / d5g56w"))
      return;
//--- Prepare M synchronized predictions for the last known tick
//--- (last one is an actual value of the trend, previous M-1 predictions
//--- are predictions from differently positioned sliding windows).
   CApServ::RMatrixSetLengthAtLeast(s.m_FcTrendM,m,winw);
   CApServ::RVectorSetLengthAtLeast(s.m_Tmp0,MathMax(m,s.m_NBasis));
   CApServ::RVectorSetLengthAtLeast(s.m_Tmp1,winw);
   for(int k=0; k<m; k++)
     {
      //--- Perform prediction for rows [0,K-1]
      CAblas::RMatrixGemVect(k,winw-1,1.0,s.m_FcTrendM,0,1,0,s.m_ForecastA,0,0.0,s.m_Tmp0,0);
      for(int i=0; i<k; i++)
        {
         for(int j=1; j<winw; j++)
            s.m_FcTrendM.Set(i,j-1,s.m_FcTrendM.Get(i,j));
         s.m_FcTrendM.Set(i,winw-1,s.m_Tmp0[i]);
        }
      //--- Perform trend extraction for row K, add it to dataset
      if(smooth)
        {
         CAblas::RMatrixGemVect(s.m_NBasis,winw,1.0,s.m_BasisT,0,0,0,data,i1-winw-(m-1-k),0.0,s.m_Tmp0,0);
         CAblas::RMatrixGemVect(s.m_WindowWidth,s.m_NBasis,1.0,s.m_Basis,0,0,0,s.m_Tmp0,0,0.0,s.m_Tmp1,0);
         if(s.m_FcTrendM.Cols()==s.m_Tmp1.Size())
            s.m_FcTrendM.Row(k,s.m_Tmp1);
         else
            for(int j=0; j<winw; j++)
               s.m_FcTrendM.Set(k,j,s.m_Tmp1[j]);
        }
      else
         for(int j=0; j<winw; j++)
            s.m_FcTrendM.Set(k,j,data[i1-winw-(m-1-k)+j]);
     }
//--- Now we have M synchronized predictions of the sequence state at the last
//--- know moment (last "prediction" is just a copy of the trend). Let's start
//--- batch prediction!
   for(int k=0; k<forecastlen; k++)
     {
      CAblas::RMatrixGemVect(m,winw-1,1.0,s.m_FcTrendM,0,1,0,s.m_ForecastA,0,0.0,s.m_Tmp0,0);
      trend.Set(offs+k,0.0);
      for(int i=0; i<=m-1; i++)
        {
         for(int j=1; j<winw; j++)
            s.m_FcTrendM.Set(i,j-1,s.m_FcTrendM.Get(i,j));
         s.m_FcTrendM.Set(i,winw-1,s.m_Tmp0[i]);
         trend.Add(offs+k,s.m_Tmp0[i]);
        }
      trend.Mul(offs+k,1.0/m);
     }
  }
//+------------------------------------------------------------------+
//| This function extracts updates from real-time queue and applies  |
//| them to the S.XXT matrix. XXT is premultiplied by Beta, which can|
//| be 0.0 for initial creation, 1.0 for subsequent updates, or even |
//| within(0, 1) for some kind of updates with decay.                |
//| INPUT PARAMETERS:                                                |
//|   S           -  model                                           |
//|   Beta        -  >= 0, coefficient to premultiply XXT            |
//|   Cnt         -  0 < Cnt <= S.RTQueueCnt, number of updates      |
//|                  to extract from the end of the queue            |
//| OUTPUT PARAMETERS:                                               |
//|   S           -  S.XXT updated, S.RTQueueCnt decreased           |
//+------------------------------------------------------------------+
void CSSA::RealtimeDequeue(CSSAModel &s,double beta,int cnt)
  {
   int winw=s.m_WindowWidth;
//--- check
   if(!CAp::Assert(cnt>0,__FUNCTION__+": RealTimeDequeue() integrity check failed / 43tdv"))
      return;
   if(!CAp::Assert(MathIsValidNumber(beta) && beta>=0.0,__FUNCTION__+": RealTimeDequeue() integrity check failed / 5gdg6"))
      return;
   if(!CAp::Assert(cnt<=s.m_RtqueueCnt,__FUNCTION__+": RealTimeDequeue() integrity check failed / 547yh"))
      return;
   if(!CAp::Assert(s.m_XXT.Cols()>=s.m_WindowWidth,__FUNCTION__+": RealTimeDequeue() integrity check failed / 54bf4"))
      return;
   if(!CAp::Assert(s.m_XXT.Rows()>=s.m_WindowWidth,__FUNCTION__+": RealTimeDequeue() integrity check failed / 9gdfn"))
      return;
//--- Premultiply XXT by Beta
   if(beta!=0.0)
      s.m_XXT*=beta;
   else
      s.m_XXT.Fill(0,winw,winw);
//--- Dequeue
   UpdateXXTPrepare(s,cnt,winw,s.m_MemoryLimit);
   for(int i=0; i<cnt; i++)
     {
      UpdateXXTSend(s,s.m_SequenceData,s.m_Rtqueue[s.m_RtqueueCnt-1],s.m_XXT);
      s.m_RtqueueCnt--;
     }
   UpdateXXTFinalize(s,s.m_XXT);
  }
//+------------------------------------------------------------------+
//| This function prepares batch buffer for XXT update. The idea is  |
//| that we send a stream of "XXT += u*u'" updates, and we want to   |
//| package them into one big matrix update U*U', applied with SYRK()|
//| kernel, but U can consume too much memory, so we want to         |
//| transparently divide it into few smaller chunks.                 |
//| This set of functions solves this problem:                       |
//|      * UpdateXXTPrepare()    prepares temporary buffers          |
//|      * UpdateXXTSend()       sends next u to the buffer,         |
//|                              possibly initiating next SYRK()     |
//|      * UpdateXXTFinalize()   performs last SYRK() update         |
//| INPUT PARAMETERS:                                                |
//|   S           -  model, only fields with UX prefix are used      |
//|   UpdateSize  -  number of updates                               |
//|   WindowWidth -  window width, > 0                               |
//|   MemoryLimit -  memory limit, non-positive value means no limit |
//| OUTPUT PARAMETERS:                                               |
//|   S           -  UX temporaries updated                          |
//+------------------------------------------------------------------+
void CSSA::UpdateXXTPrepare(CSSAModel &s,int updatesize,int windowwidth,
                            int memorylimit)
  {
//--- check
   if(!CAp::Assert(windowwidth>0,__FUNCTION__+": WinW<=0"))
      return;

   s.m_UxBatchLimit=MathMax(updatesize,1);
   if(memorylimit>0)
      s.m_UxBatchLimit=MathMin(s.m_UxBatchLimit,MathMax(memorylimit/windowwidth,4*windowwidth));
   s.m_UxBatchWidth=windowwidth;
   s.m_UxBatchSize=0;
   if(s.m_UxBatch.Cols()!=windowwidth)
      s.m_UxBatch.Resize(0,0);
   CApServ::RMatrixSetLengthAtLeast(s.m_UxBatch,s.m_UxBatchLimit,windowwidth);
  }
//+------------------------------------------------------------------+
//| This function sends update u*u' to the batch buffer.             |
//| INPUT PARAMETERS:                                                |
//|   S           -  model, only fields with UX prefix are used      |
//|   U           -  WindowWidth - sized update, starts at I0        |
//|   I0          -  starting position for update                    |
//| OUTPUT PARAMETERS:                                               |
//|   S           -  UX temporaries updated                          |
//|   XXT         -  array[WindowWidth, WindowWidth], in the middle  |
//|                  of update. All intermediate updates are applied |
//|                  to the upper triangle.                          |
//+------------------------------------------------------------------+
void CSSA::UpdateXXTSend(CSSAModel &s,CRowDouble &u,int i0,
                         CMatrixDouble &xxt)
  {
//--- check
   if(!CAp::Assert(i0+s.m_UxBatchWidth-1<u.Size(),__FUNCTION__+": incorrect U size"))
      return;
   if(!CAp::Assert(s.m_UxBatchSize>=0,__FUNCTION__+": integrity check failure"))
      return;
   if(!CAp::Assert(s.m_UxBatchSize<=s.m_UxBatchLimit,__FUNCTION__+": integrity check failure"))
      return;
   if(!CAp::Assert(s.m_UxBatchLimit>=1,__FUNCTION__+": integrity check failure"))
      return;
//--- Send pending batch if full
   if(s.m_UxBatchSize==s.m_UxBatchLimit)
     {
      CAblas::RMatrixSyrk(s.m_UxBatchWidth,s.m_UxBatchSize,1.0,s.m_UxBatch,0,0,2,1.0,xxt,0,0,true);
      s.m_UxBatchSize=0;
     }
//--- Append update to batch
   int i1_=i0;
   for(int i_=0; i_<s.m_UxBatchWidth; i_++)
      s.m_UxBatch.Set(s.m_UxBatchSize,i_,u[i_+i1_]);
   s.m_UxBatchSize++;
  }
//+------------------------------------------------------------------+
//| This function finalizes batch buffer. Call it after the last     |
//| update.                                                          |
//| INPUT PARAMETERS:                                                |
//|   S           -  model, only fields with UX prefix are used      |
//| OUTPUT PARAMETERS:                                               |
//|   S           -  UX temporaries updated                          |
//|   XXT         -  array[WindowWidth, WindowWidth], updated with   |
//|                  all previous updates, both triangles of the     |
//|                  symmetric matrix are present.                   |
//+------------------------------------------------------------------+
void CSSA::UpdateXXTFinalize(CSSAModel &s,CMatrixDouble &xxt)
  {
//--- check
   if(!CAp::Assert(s.m_UxBatchSize>=0,__FUNCTION__+": integrity check failure"))
      return;
   if(!CAp::Assert(s.m_UxBatchSize<=s.m_UxBatchLimit,__FUNCTION__+": integrity check failure"))
      return;
   if(!CAp::Assert(s.m_UxBatchLimit>=1,__FUNCTION__+": integrity check failure"))
      return;
//---
   if(s.m_UxBatchSize>0)
     {
      CAblas::RMatrixSyrk(s.m_UxBatchWidth,s.m_UxBatchSize,1.0,s.m_UxBatch,0,0,2,1.0,s.m_XXT,0,0,true);
      s.m_UxBatchSize=0;
     }
   CAblas::RMatrixEnforceSymmetricity(s.m_XXT,s.m_UxBatchWidth,true);
  }
//+------------------------------------------------------------------+
//| Buffer object which is used to perform various requests (usually |
//| model inference) in the multithreaded mode (multiple threads     |
//| working with same KNN object).                                   |
//| This object should be created with KNNCreateBuffer().            |
//+------------------------------------------------------------------+
struct CKNNBuffer
  {
   CKDTreeRequestBuffer m_treebuf;
   CRowDouble        m_x;
   CRowDouble        m_y;
   CRowInt           m_tags;
   CMatrixDouble     m_xy;
   //---
                     CKNNBuffer(void) {}
                    ~CKNNBuffer(void) {}
   void              Copy(const CKNNBuffer &obj);
   //--- overloading
   void              operator=(const CKNNBuffer &obj) { Copy(obj); }
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CKNNBuffer::Copy(const CKNNBuffer &obj)
  {
   m_treebuf=obj.m_treebuf;
   m_x=obj.m_x;
   m_y=obj.m_y;
   m_tags=obj.m_tags;
   m_xy=obj.m_xy;
  }
//+------------------------------------------------------------------+
//| A KNN builder object; this object encapsulates dataset and all   |
//| related settings, it is used to create an actual instance of KNN |
//| model.                                                           |
//+------------------------------------------------------------------+
struct CKNNBuilder
  {
   int               m_dstype;
   int               m_npoints;
   int               m_nvars;
   bool              m_iscls;
   int               m_nout;
   CMatrixDouble     m_dsdata;
   CRowDouble        m_dsrval;
   CRowInt           m_dsival;
   int               m_knnnrm;
   //---
                     CKNNBuilder(void);
                    ~CKNNBuilder(void) {}
   void              Copy(const CKNNBuilder &obj);
   //--- overloading
   void              operator=(const CKNNBuilder &obj) { Copy(obj); }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CKNNBuilder::CKNNBuilder(void)
  {
   m_dstype=0;
   m_npoints=0;
   m_nvars=0;
   m_iscls=false;
   m_nout=0;
   m_knnnrm=0;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CKNNBuilder::Copy(const CKNNBuilder &obj)
  {
   m_dstype=obj.m_dstype;
   m_npoints=obj.m_npoints;
   m_nvars=obj.m_nvars;
   m_iscls=obj.m_iscls;
   m_nout=obj.m_nout;
   m_dsdata=obj.m_dsdata;
   m_dsrval=obj.m_dsrval;
   m_dsival=obj.m_dsival;
   m_knnnrm=obj.m_knnnrm;
  }
//+------------------------------------------------------------------+
//| KNN model, can be used for classification or regression          |
//+------------------------------------------------------------------+
struct CKNNModel
  {
   int               m_nvars;
   int               m_nout;
   int               m_k;
   double            m_eps;
   bool              m_iscls;
   bool              m_isdummy;
   CKDTree           m_tree;
   CKNNBuffer        m_buffer;
   //---
                     CKNNModel(void);
                    ~CKNNModel(void) {}
   //---
   void              Copy(const CKNNModel &obj);
   //--- overloading
   void              operator=(const CKNNModel &obj) { Copy(obj); }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CKNNModel::CKNNModel(void)
  {
   m_nvars=0;
   m_nout=0;
   m_k=0;
   m_eps=0;
   m_iscls=false;
   m_isdummy=false;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CKNNModel::Copy(const CKNNModel &obj)
  {
   m_nvars=obj.m_nvars;
   m_nout=obj.m_nout;
   m_k=obj.m_k;
   m_eps=obj.m_eps;
   m_iscls=obj.m_iscls;
   m_isdummy=obj.m_isdummy;
   m_tree=obj.m_tree;
   m_buffer=obj.m_buffer;
  }
//+------------------------------------------------------------------+
//| KNN training report.                                             |
//| Following fields store training set errors:                      |
//|   * relclserror  -  fraction of misclassified cases, [0,1]       |
//|   * avgce        -  average cross-entropy in bits per symbol     |
//|   * rmserror     -  root-mean-square error                       |
//|   * avgerror     -  average error                                |
//|   * avgrelerror  -  average relative error                       |
//| For classification problems:                                     |
//|   * RMS, AVG and AVGREL errors are calculated for posterior      |
//|     probabilities                                                |
//| For regression problems:                                         |
//|   * RELCLS and AVGCE errors are zero                             |
//+------------------------------------------------------------------+
struct CKNNReport
  {
   double            m_RelCLSError;
   double            m_AvgCE;
   double            m_RMSError;
   double            m_AvgError;
   double            m_AvgRelError;
   //---
                     CKNNReport(void) { ZeroMemory(this); }
                    ~CKNNReport(void) {}
   //---
   void              Copy(const CKNNReport &obj);
   //--- overloading
   void              operator=(const CKNNReport &obj) { Copy(obj); }
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CKNNReport::Copy(const CKNNReport &obj)
  {
   m_RelCLSError=obj.m_RelCLSError;
   m_AvgCE=obj.m_AvgCE;
   m_RMSError=obj.m_RMSError;
   m_AvgError=obj.m_AvgError;
   m_AvgRelError=obj.m_AvgRelError;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CKNN
  {
public:
   //--- constant
   static         const int m_knnfirstversion;

   static void       KNNCreateBuffer(CKNNModel &model,CKNNBuffer &buf);
   static void       KNNBuilderCreate(CKNNBuilder &s);
   static void       KNNBuilderSetDatasetReg(CKNNBuilder &s,CMatrixDouble &xy,int npoints,int nvars,int nout);
   static void       KNNBuilderSetDatasetCLS(CKNNBuilder &s,CMatrixDouble &xy,int npoints,int nvars,int nclasses);
   static void       KNNBuilderSetNorm(CKNNBuilder &s,int nrmtype);
   static void       KNNBuilderBuildKNNModel(CKNNBuilder &s,int k,double eps,CKNNModel &model,CKNNReport &rep);
   static void       KNNRewriteKEps(CKNNModel &model,int k,double eps);
   static void       KNNProcess(CKNNModel &model,CRowDouble &x,CRowDouble &y);
   static double     KNNProcess0(CKNNModel &model,CRowDouble &x);
   static int        KNNClassify(CKNNModel &model,CRowDouble &x);
   static void       KNNProcessI(CKNNModel &model,CRowDouble &x,CRowDouble &y);
   static void       KNNTsProcess(CKNNModel &model,CKNNBuffer &buf,CRowDouble &x,CRowDouble &y);
   static double     KNNRelClsError(CKNNModel &model,CMatrixDouble &xy,int npoints);
   static double     KNNAvgCE(CKNNModel &model,CMatrixDouble &xy,int npoints);
   static double     KNNRMSError(CKNNModel &model,CMatrixDouble &xy,int npoints);
   static double     KNNAvgError(CKNNModel &model,CMatrixDouble &xy,int npoints);
   static double     CKNN::KNNAvgRelError(CKNNModel &model,CMatrixDouble &xy,int npoints);
   static void       KNNAllErrors(CKNNModel &model,CMatrixDouble &xy,int npoints,CKNNReport &rep);
   static void       CKNN::KNNAlloc(CSerializer &s,CKNNModel &model);
   static void       KNNSerialize(CSerializer &s,CKNNModel &model);
   static void       KNNUnserialize(CSerializer &s,CKNNModel &model);

private:
   static void       ClearReport(CKNNReport &rep);
   static void       ProcessInternal(CKNNModel &model,CKNNBuffer &buf);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
const int CKNN::m_knnfirstversion=0;
//+------------------------------------------------------------------+
//| This function creates buffer structure which can be used to      |
//| perform parallel KNN requests.                                   |
//| KNN subpackage provides two sets of computing functions - ones   |
//| which use internal buffer of KNN model (these  functions are     |
//| single-threaded because they use same buffer, which can not      |
//| shared between threads), and ones which use external buffer.     |
//| This function is used to initialize external buffer.             |
//| INPUT PARAMETERS                                                 |
//|   Model       -  KNN model which is associated with newly created|
//|                  buffer                                          |
//| OUTPUT PARAMETERS                                                |
//|   Buf         -  external buffer.                                |
//| IMPORTANT: buffer object should be used only with model which was|
//|            used to initialize buffer. Any attempt to  use  buffer|
//|            with different object is dangerous - you  may   get   |
//|            integrity check failure (exception) because sizes of  |
//|            internal arrays do not fit to dimensions of the model |
//|            structure.                                            |
//+------------------------------------------------------------------+
void CKNN::KNNCreateBuffer(CKNNModel &model,CKNNBuffer &buf)
  {
   if(!model.m_isdummy)
      CNearestNeighbor::KDTreeCreateRequestBuffer(model.m_tree,buf.m_treebuf);
   buf.m_x.Resize(model.m_nvars);
   buf.m_y.Resize(model.m_nout);
  }
//+------------------------------------------------------------------+
//| This subroutine creates KNNBuilder object which is used to train |
//| KNN models.                                                      |
//| By default, new builder stores empty dataset and some reasonable |
//| default settings. At the very least, you should specify dataset  |
//| prior to building KNN model. You can also tweak settings of the  |
//| model construction algorithm (recommended, although default      |
//| settings should work well).                                      |
//| Following actions are mandatory:                                 |
//|   * calling knnbuildersetdataset() to specify dataset            |
//|   * calling knnbuilderbuildknnmodel() to build KNN model using   |
//|     current dataset and default settings                         |
//| Additionally, you may call:                                      |
//|   * KNNBuilderSetNorm() to change norm being used                |
//| INPUT PARAMETERS:                                                |
//|   none                                                           |
//| OUTPUT PARAMETERS:                                               |
//|   S           -  KNN builder                                     |
//+------------------------------------------------------------------+
void CKNN::KNNBuilderCreate(CKNNBuilder &s)
  {
//--- Empty dataset
   s.m_dstype=-1;
   s.m_npoints=0;
   s.m_nvars=0;
   s.m_iscls=false;
   s.m_nout=1;
//--- Default training settings
   s.m_knnnrm=2;
  }
//+------------------------------------------------------------------+
//| Specifies regression problem (one or more continuous output      |
//| variables are predicted). There also exists "classification"     |
//| version of this function.                                        |
//| This subroutine adds dense dataset to the internal storage of the|
//| builder object. Specifying your dataset in the dense format means|
//| that the dense version of the KNN construction algorithm will be |
//| invoked.                                                         |
//| INPUT PARAMETERS:                                                |
//|   S           -  KNN builder object                              |
//|   XY          -  array[NPoints,NVars+NOut] (note: actual size can|
//|                  be larger, only leading part is used anyway),   |
//|                  dataset:                                        |
//|                  * first NVars elements of each row store values |
//|                    of the independent variables                  |
//|                  * next NOut elements store  values  of  the     |
//|                    dependent variables                           |
//|   NPoints     -  number of rows in the dataset, NPoints>=1       |
//|   NVars       -  number of independent variables, NVars>=1       |
//|   NOut        -  number of dependent variables, NOut>=1          |
//| OUTPUT PARAMETERS:                                               |
//|   S           -  KNN builder                                     |
//+------------------------------------------------------------------+
void CKNN::KNNBuilderSetDatasetReg(CKNNBuilder &s,CMatrixDouble &xy,
                                   int npoints,int nvars,int nout)
  {
//--- Check parameters
   if(!CAp::Assert(npoints>=1,__FUNCTION__+": npoints<1"))
      return;
   if(!CAp::Assert(nvars>=1,__FUNCTION__+": nvars<1"))
      return;
   if(!CAp::Assert(nout>=1,__FUNCTION__+": nout<1"))
      return;
   if(!CAp::Assert(xy.Rows()>=npoints,__FUNCTION__+": rows(xy)<npoints"))
      return;
   if(!CAp::Assert(xy.Cols()>=nvars+nout,__FUNCTION__+": cols(xy)<nvars+nout"))
      return;
   if(!CAp::Assert(CApServ::IsFiniteMatrix(xy,npoints,nvars+nout),__FUNCTION__+": xy parameter contains INFs or NANs"))
      return;
//--- Set dataset
   s.m_dstype=0;
   s.m_iscls=false;
   s.m_npoints=npoints;
   s.m_nvars=nvars;
   s.m_nout=nout;
   ulong parts[]={nvars,nout};
   matrix<double> splited[];
   xy.Split(parts,1,splited);
   s.m_dsdata=splited[0];
   splited[1].Reshape(1,npoints*nout);
   s.m_dsrval=splited[1].Row(0);
  }
//+------------------------------------------------------------------+
//| Specifies classification problem (two or more classes are        |
//| predicted). There also exists "regression" version of this       |
//| function.                                                        |
//| This subroutine adds dense dataset to the internal storage of the|
//| builder object. Specifying your dataset in the dense format means|
//| that the dense version of the KNN construction algorithm will be |
//| invoked.                                                         |
//| INPUT PARAMETERS:                                                |
//|   S        -  KNN builder object                                 |
//|   XY       -  array[NPoints, NVars + 1] (note: actual size can be|
//|               larger, only leading part is used anyway), dataset:|
//|               * first NVars elements of each row store values of |
//|                 the independent variables                        |
//|               * next element stores class index, in [0, NClasses)|
//|   NPoints  -  number of rows in the dataset, NPoints >= 1        |
//|   NVars    -  number of independent variables, NVars >= 1        |
//|   NClasses -  number of classes, NClasses >= 2                   |
//| OUTPUT PARAMETERS:                                               |
//|   S        -  KNN builder                                        |
//+------------------------------------------------------------------+
void CKNN::KNNBuilderSetDatasetCLS(CKNNBuilder &s,
                                   CMatrixDouble &xy,
                                   int npoints,int nvars,int nclasses)
  {
//--- Check parameters
   if(!CAp::Assert(npoints>=1,__FUNCTION__+": npoints<1"))
      return;
   if(!CAp::Assert(nvars>=1,__FUNCTION__+": nvars<1"))
      return;
   if(!CAp::Assert(nclasses>=2,__FUNCTION__+": nclasses<2"))
      return;
   if(!CAp::Assert(xy.Rows()>=npoints,__FUNCTION__+": rows(xy)<npoints"))
      return;
   if(!CAp::Assert(xy.Cols()>=nvars+1,__FUNCTION__+": cols(xy)<nvars+1"))
      return;
   if(!CAp::Assert(CApServ::IsFiniteMatrix(xy,npoints,nvars+1),__FUNCTION__+": xy parameter contains INFs or NANs"))
      return;
   vector<double> y=xy.Col(nvars);
   if(!CAp::Assert(y.Min()>=0 && y.Max()<nclasses,__FUNCTION__+": last column of xy contains invalid class number"))
      return;
//--- Set dataset
   s.m_iscls=      true;
   s.m_dstype=     0;
   s.m_npoints=    npoints;
   s.m_nvars=      nvars;
   s.m_nout=       nclasses;
   ulong parts[]={nvars,nclasses};
   matrix<double> splited[];
   xy.Split(parts,1,splited);
   s.m_dsdata=splited[0];
   s.m_dsival.Resize(npoints);
   for(int i=0; i<npoints; i++)
      s.m_dsival.Set(i,(int)MathRound(y[i]));
  }
//+------------------------------------------------------------------+
//| This function sets norm type used for neighbor search.           |
//| INPUT PARAMETERS:                                                |
//|   S           -  decision forest builder object                  |
//|   NormType    -  norm type:                                      |
//|                  * 0      inf-norm                               |
//|                  * 1      1-norm                                 |
//|                  * 2      Euclidean norm(default)                |
//| OUTPUT PARAMETERS:                                               |
//|   S           -  decision forest builder                         |
//+------------------------------------------------------------------+
void CKNN::KNNBuilderSetNorm(CKNNBuilder &s,int nrmtype)
  {
//--- check
   if(!CAp::Assert(nrmtype==0 || nrmtype==1 || nrmtype==2,__FUNCTION__+": unexpected norm type"))
      return;
   s.m_knnnrm=nrmtype;
  }
//+------------------------------------------------------------------+
//| This subroutine builds KNN model according to current settings,  |
//| using dataset internally stored in the builder object.           |
//| The model being built performs inference using Eps-approximate   |
//| K nearest neighbors search algorithm, with:                      |
//|     *K=1,  Eps=0 corresponding to the "nearest neighbor    |
//|                        algorithm"                                |
//|     *K>1,  Eps=0 corresponding to the "K nearest neighbors |
//|                        algorithm"                                |
//|     *K>=1, Eps>0 corresponding to "approximate nearest     |
//|                        neighbors algorithm"                      |
//| An approximate KNN is a good option for high-dimensional datasets|
//| (exact KNN works slowly when dimensions count grows).            |
//| An ALGLIB implementation of kd-trees is used to perform k-nn     |
//| searches.                                                        |
//| INPUT PARAMETERS:                                                |
//|   S           -  KNN builder object                              |
//|   K           -  number of neighbors to search for, K >= 1       |
//|   Eps         -  approximation factor:                           |
//|               * Eps = 0 means that exact kNN search is performed |
//|               * Eps > 0 means that(1 + Eps) - approximate search |
//|                         is performed                             |
//| OUTPUT PARAMETERS:                                               |
//|   Model       -  KNN model                                       |
//|   Rep         -  report                                          |
//+------------------------------------------------------------------+
void CKNN::KNNBuilderBuildKNNModel(CKNNBuilder &s,int k,double eps,
                                   CKNNModel &model,CKNNReport &rep)
  {
//--- create variables
   int  npoints=s.m_npoints;
   int  nvars=s.m_nvars;
   int  nout=s.m_nout;
   bool iscls=s.m_iscls;
   CMatrixDouble xy;
   CRowInt tags;
//--- Check settings
   if(!CAp::Assert(k>=1,__FUNCTION__+": k<1"))
      return;
   if(!CAp::Assert(MathIsValidNumber(eps) && eps>=0.0,__FUNCTION__+": eps<0"))
      return;
//--- Prepare output
   ClearReport(rep);
   model.m_nvars=nvars;
   model.m_nout=nout;
   model.m_iscls=iscls;
   model.m_k=k;
   model.m_eps=eps;
   model.m_isdummy=false;
//--- Quick exit for empty dataset
   if(s.m_dstype==-1)
     {
      model.m_isdummy=true;
      return;
     }
//--- Build kd-tree
   if(iscls)
     {
      xy=s.m_dsdata;
      xy.Resize(npoints,nvars+1);
      xy.Col(nvars,s.m_dsival);
      tags=s.m_dsival;
      CNearestNeighbor::KDTreeBuildTagged(xy,tags,npoints,nvars,0,s.m_knnnrm,model.m_tree);
     }
   else
     {
      xy=s.m_dsdata;
      xy.Resize(npoints,nvars+nout);
      for(int i=0; i<npoints; i++)
         for(int j=0; j<nout; j++)
            xy.Set(i,nvars+j,s.m_dsrval[i*nout+j]);
      CNearestNeighbor::KDTreeBuild(xy,npoints,nvars,nout,s.m_knnnrm,model.m_tree);
     }
//--- Build buffer
   KNNCreateBuffer(model,model.m_buffer);
//--- Report
   KNNAllErrors(model,xy,npoints,rep);
  }
//+------------------------------------------------------------------+
//| Changing search settings of KNN model.                           |
//| K and EPS parameters of KNN(AKNN) search are specified  during   |
//| model construction. However, plain KNN algorithm with Euclidean  |
//| distance allows you to change them at any moment.                |
//| NOTE: future versions of KNN model may support advanced versions |
//|       of KNN, such as NCA or LMNN. It is possible that such      |
//|       algorithms won't allow you to change search settings on the|
//|       fly. If you call this function for an algorithm which does |
//|       not support on-the-fly changes, it  will throw an exception|
//| INPUT PARAMETERS:                                                |
//|   Model       -  KNN model                                       |
//|   K           -  K >= 1, neighbors count                         |
//|   EPS         -  accuracy of the EPS-approximate NN search. Set  |
//|                  to 0.0, if you want to perform "classic" KNN    |
//|                  search. Specify larger values if you need to    |
//|                  speed-up high-dimensional KNN queries.          |
//| OUTPUT PARAMETERS:                                               |
//|      nothing on success, exception on failure                    |
//+------------------------------------------------------------------+
void CKNN::KNNRewriteKEps(CKNNModel &model,int k,double eps)
  {
//--- check
   if(!CAp::Assert(k>=1,__FUNCTION__+": k<1"))
      return;
   if(!CAp::Assert(MathIsValidNumber(eps) && eps>=0.0,__FUNCTION__+": eps<0"))
      return;
//--- change values
   model.m_k=k;
   model.m_eps=eps;
  }
//+------------------------------------------------------------------+
//| Inference using KNN model.                                       |
//| See also KNNProcess0(), KNNProcessI() and KNNClassify() for      |
//| options with a bit more convenient interface.                    |
//| INPUT PARAMETERS:                                                |
//|   Model    -  KNN model                                          |
//|   X        -  input vector,  array[0..NVars - 1].                |
//|   Y        -  possible preallocated buffer. Reused if long enough|
//| OUTPUT PARAMETERS:                                               |
//|   Y        -  result. Regression estimate when solving regression|
//|               task, vector of posterior probabilities for        |
//|               classification task.                               |
//+------------------------------------------------------------------+
void CKNN::KNNProcess(CKNNModel &model,CRowDouble &x,CRowDouble &y)
  {
   KNNTsProcess(model,model.m_buffer,x,y);
  }
//+------------------------------------------------------------------+
//| This function returns first component of the inferred vector     |
//| (i.e.one with index #0).                                         |
//| It is a convenience wrapper for KNNProcess() intended for either:|
//|      * 1 - dimensional regression problems                       |
//|      * 2 - class classification problems                         |
//| In the former case this function returns inference result as     |
//| scalar, which is definitely more convenient that wrapping it as  |
//| vector. In the latter case it returns probability of object      |
//| belonging to class #0.                                           |
//| If you call it for anything different from two cases above, it   |
//| will work as defined, i.e. return y[0], although it is of less   |
//| use in such cases.                                               |
//| INPUT PARAMETERS:                                                |
//|   Model    -  KNN model                                          |
//|   X        -  input vector,  array[0..NVars - 1].                |
//| RESULT:                                                          |
//|   Y[0]                                                           |
//+------------------------------------------------------------------+
double CKNN::KNNProcess0(CKNNModel &model,CRowDouble &x)
  {
//--- copy
   model.m_buffer.m_x=x;
//--- function call
   ProcessInternal(model,model.m_buffer);
//--- return result
   return(model.m_buffer.m_y[0]);
  }
//+------------------------------------------------------------------+
//| This function returns most probable class number for an  input X.|
//| It is same as calling KNNProcess(model, x, y), then determining  |
//| i = argmax(y[i]) and returning i.                                |
//| A class number in [0, NOut) range in returned for classification |
//| problems, -1 is returned when this function is called for        |
//| regression problems.                                             |
//| INPUT PARAMETERS:                                                |
//|   Model    -  KNN model                                          |
//|   X        -  input vector,  array[0..NVars - 1].                |
//| RESULT:                                                          |
//|   class number, -1 for regression tasks                          |
//+------------------------------------------------------------------+
int CKNN::KNNClassify(CKNNModel &model,CRowDouble &x)
  {
//--- check
   if(!model.m_iscls)
      return (-1);
//--- function call
   ProcessInternal(model,model.m_buffer);
//--- return result
   return ((int)model.m_buffer.m_y.ArgMax());
  }
//+------------------------------------------------------------------+
//| 'interactive' variant of KNNProcess() which support constructs   |
//| like "y = KNNProcessI(model,x)" and interactive mode of the      |
//| interpreter.                                                     |
//| This function allocates new array on each call, so it is         |
//| significantly slower than its 'non-interactive' counterpart, but |
//| it is more convenient when you call it from command line.        |
//+------------------------------------------------------------------+
void CKNN::KNNProcessI(CKNNModel &model,CRowDouble &x,CRowDouble &y)
  {
   y.Resize(0);
   KNNProcess(model,x,y);
  }
//+------------------------------------------------------------------+
//| Thread - safe procesing using external buffer for temporaries.   |
//| This function is thread-safe(i.e. you can use same KNN model from|
//| multiple threads) as long as you use different buffer objects for|
//| different threads.                                               |
//| INPUT PARAMETERS:                                                |
//|   Model    -  KNN model                                          |
//|   Buf      -  buffer object, must be allocated specifically for  |
//|               this model with KNNCreateBuffer().                 |
//|   X        -  input vector,  array[NVars]                        |
//| OUTPUT PARAMETERS:                                               |
//|   Y        -  result, array[NOut]. Regression estimate when      |
//|               solving regression task, vector of posterior       |
//|               probabilities for a classification task.           |
//+------------------------------------------------------------------+
void CKNN::KNNTsProcess(CKNNModel &model,CKNNBuffer &buf,CRowDouble &x,
                        CRowDouble &y)
  {
   int nout=model.m_nout;
//--- copy
   buf.m_x=x;
   ProcessInternal(model,buf);
//--- return result
   if(y.Size()<=nout)
      y=buf.m_y;
   else
     for(int i=0;i<nout;i++)
       y.Set(i,buf.m_y[i]);
  }
//+------------------------------------------------------------------+
//| Relative classification error on the test set                    |
//| INPUT PARAMETERS:                                                |
//|   Model    -  KNN model                                          |
//|   XY       -  test set                                           |
//|   NPoints  -  test set size                                      |
//| RESULT:                                                          |
//|   percent of incorrectly classified cases.                       |
//|   Zero if model solves regression task.                          |
//| NOTE: if you need several different kinds of error metrics, it is|
//|       better to use KNNAllErrors() which computes all error      |
//|       metric with just one pass over dataset.                    |
//+------------------------------------------------------------------+
double CKNN::KNNRelClsError(CKNNModel &model,CMatrixDouble &xy,int npoints)
  {
   CKNNReport rep;
//--- function call
   KNNAllErrors(model,xy,npoints,rep);
//--- return result
   return(rep.m_RelCLSError);
  }
//+------------------------------------------------------------------+
//| Average cross-entropy (in bits per element) on the test set      |
//| INPUT PARAMETERS:                                                |
//|   Model    -  KNN model                                          |
//|   XY       -  test set                                           |
//|   NPoints  -  test set size                                      |
//| RESULT:                                                          |
//|   CrossEntropy / NPoints.                                        |
//|   Zero if model solves regression task.                          |
//| NOTE: the cross-entropy metric is too unstable when used         |
//|       to evaluate KNN models (such models can report exactly     |
//|       zero probabilities), so we do not recommend using it.      |
//| NOTE:  if you need several different kinds of error metrics, it  |
//|        is better to use KNNAllErrors() which computes all error  |
//|        metric with just one pass over dataset.                   |
//+------------------------------------------------------------------+
double CKNN::KNNAvgCE(CKNNModel &model,CMatrixDouble &xy,int npoints)
  {
   CKNNReport rep;
//--- function call
   KNNAllErrors(model,xy,npoints,rep);
//--- return result
   return(rep.m_AvgCE);
  }
//+------------------------------------------------------------------+
//| RMS error on the test set.                                       |
//| Its meaning for regression task is obvious. As for classification|
//| problems, RMS error means error when estimating posterior        |
//| probabilities.                                                   |
//| INPUT PARAMETERS:                                                |
//|   Model    -  KNN model                                          |
//|   XY       -  test set                                           |
//|   NPoints  -  test set size                                      |
//| RESULT:                                                          |
//|   root mean square error.                                        |
//| NOTE: if you need several different kinds of error metrics, it   |
//|       is better to use KNNAllErrors() which computes all error   |
//|       metric with just one pass over dataset.                    |
//+------------------------------------------------------------------+
double CKNN::KNNRMSError(CKNNModel &model,CMatrixDouble &xy,int npoints)
  {
   CKNNReport rep;
//--- function call
   KNNAllErrors(model,xy,npoints,rep);
//--- return result
   return(rep.m_RMSError);
  }
//+------------------------------------------------------------------+
//| Average error on the test set                                    |
//| Its meaning for regression task is obvious. As for classification|
//| problems, average error means error when estimating posterior    |
//| probabilities.                                                   |
//| INPUT PARAMETERS:                                                |
//|   Model    -  KNN model                                          |
//|   XY       -  test set                                           |
//|   NPoints  -  test set size                                      |
//| RESULT:                                                          |
//|   average error                                                  |
//| NOTE: if you need several different kinds of error metrics, it   |
//|       is better to use KNNAllErrors() which computes all error   |
//|       metric with just one pass over dataset.                    |
//+------------------------------------------------------------------+
double CKNN::KNNAvgError(CKNNModel &model,CMatrixDouble &xy,int npoints)
  {
   CKNNReport rep;
//--- function call
   KNNAllErrors(model,xy,npoints,rep);
//--- return result
   return(rep.m_AvgError);
  }
//+------------------------------------------------------------------+
//| Average relative error on the test set                           |
//| Its meaning for regression task is obvious. As for classification|
//| problems, average relative error means error when estimating     |
//| posterior probabilities.                                         |
//| INPUT PARAMETERS:                                                |
//|   Model    -  KNN model                                          |
//|   XY       -  test set                                           |
//|   NPoints  -  test set size                                      |
//| RESULT:                                                          |
//|   average relative error                                         |
//| NOTE: if you need several different kinds of error metrics, it   |
//|       is better to use KNNAllErrors() which computes all error   |
//|       metric with just one pass over dataset.                    |
//+------------------------------------------------------------------+
double CKNN::KNNAvgRelError(CKNNModel &model,CMatrixDouble &xy,int npoints)
  {
   CKNNReport rep;
//--- function call
   KNNAllErrors(model,xy,npoints,rep);
//--- return result
   return(rep.m_AvgRelError);
  }
//+------------------------------------------------------------------+
//| Calculates all kinds of errors for the model in one call.        |
//| INPUT PARAMETERS:                                                |
//|   Model    -  KNN model                                          |
//|   XY       -  test set:                                          |
//|               * one row per point                                |
//|               * first NVars columns store independent variables  |
//|               * depending on problem type:                       |
//|                  * next column stores class number               |
//|                    in [0, NClasses) - for classification         |
//|                    problems                                      |
//|                  * next NOut columns store dependent             |
//|                    variables - for regression problems           |
//|   NPoints  -  test set size, NPoints >= 0                        |
//| OUTPUT PARAMETERS:                                               |
//|   Rep      -  following fields are loaded with errors for both   |
//|               regression and classification models:              |
//|               * rep.RMSError - RMS error for the output          |
//|               * rep.AvgError - average error                     |
//|               * rep.AvgRelError - average relative error         |
//|            following fields are set only for classification      |
//|            models, zero for regression ones:                     |
//|               * relclserror   - relative classification error,   |
//|                                 in [0, 1]                        |
//|               * avgce - average cross-entropy in bits per        |
//|                         dataset entry                            |
//| NOTE: the cross-entropy metric is too unstable when used to      |
//|       evaluate KNN models (such models can report exactly zero   |
//|       probabilities), so we do not recommend using it.           |
//+------------------------------------------------------------------+
void CKNN::KNNAllErrors(CKNNModel &model,CMatrixDouble &xy,
                        int npoints,CKNNReport &rep)
  {
//--- Clean up report
   ClearReport(rep);
//--- Quick exit if needed
   if(model.m_isdummy || npoints==0)
      return;
//--- create variables
   CKNNBuffer buf;
   CRowDouble desiredy;
   CRowDouble errbuf;
   int  ny=0;
   int  j=0;
   int  nvars=model.m_nvars;
   int  nout=model.m_nout;
   bool iscls=model.m_iscls;
//---
   if(iscls)
      ny=1;
   else
      ny=nout;
//--- Check input
   if(!CAp::Assert(npoints>=0,__FUNCTION__+": npoints<0"))
      return;
   if(!CAp::Assert(xy.Rows()>=npoints,__FUNCTION__+": rows(xy)<npoints"))
      return;
   if(!CAp::Assert(xy.Cols()>=nvars+ny,__FUNCTION__+": cols(xy)<nvars+nout"))
      return;
   if(!CAp::Assert(CApServ::IsFiniteMatrix(xy,npoints,nvars+ny),__FUNCTION__+": xy parameter contains INFs or NANs"))
      return;
//--- Process using local buffer
   KNNCreateBuffer(model,buf);
   if(iscls)
      CBdSS::DSErrAllocate(nout,errbuf);
   else
      CBdSS::DSErrAllocate(-nout,errbuf);
   desiredy.Resize(ny);
   ulong parts[]={nvars,nout};
   matrix<double> splitted[];
//--- check
   xy.Split(parts,1,splitted);
   for(int i=0; i<npoints; i++)
     {
      buf.m_x=splitted[0].Row(i);
      desiredy=splitted[1].Row(i);
      if(iscls)
        {
         j=(int)MathRound(desiredy[0]);
         //--- check
         if(!CAp::Assert(j>=0 && j<nout,__FUNCTION__+": one of the class labels is not in [0,NClasses)"))
            return;
        }
      ProcessInternal(model,buf);
      CBdSS::DSErrAccumulate(errbuf,buf.m_y,desiredy);
     }
   CBdSS::DSErrFinish(errbuf);
//--- Extract results
   if(iscls)
     {
      rep.m_RelCLSError=errbuf[0];
      rep.m_AvgCE=errbuf[1];
     }
   rep.m_RMSError=errbuf[2];
   rep.m_AvgError=errbuf[3];
   rep.m_AvgRelError=errbuf[4];
  }
//+------------------------------------------------------------------+
//| Serializer: allocation                                           |
//+------------------------------------------------------------------+
void CKNN::KNNAlloc(CSerializer &s,CKNNModel &model)
  {
   s.Alloc_Entry();
   s.Alloc_Entry();
   s.Alloc_Entry();
   s.Alloc_Entry();
   s.Alloc_Entry();
   s.Alloc_Entry();
   s.Alloc_Entry();
   s.Alloc_Entry();
   if(!model.m_isdummy)
      CNearestNeighbor::KDTreeAlloc(s,model.m_tree);
  }
//+------------------------------------------------------------------+
//| Serializer: serialization                                        |
//+------------------------------------------------------------------+
void CKNN::KNNSerialize(CSerializer &s,CKNNModel &model)
  {
   s.Serialize_Int(CSCodes::GetKNNSerializationCode());
   s.Serialize_Int(m_knnfirstversion);
   s.Serialize_Int(model.m_nvars);
   s.Serialize_Int(model.m_nout);
   s.Serialize_Int(model.m_k);
   s.Serialize_Double(model.m_eps);
   s.Serialize_Bool(model.m_iscls);
   s.Serialize_Bool(model.m_isdummy);
   if(!model.m_isdummy)
      CNearestNeighbor::KDTreeSerialize(s,model.m_tree);
  }
//+------------------------------------------------------------------+
//| Serializer: unserialization                                      |
//+------------------------------------------------------------------+
void CKNN::KNNUnserialize(CSerializer &s,CKNNModel &model)
  {
//--- check correctness of header
   int i0=s.Unserialize_Int();
   if(!CAp::Assert(i0==CSCodes::GetKNNSerializationCode(),__FUNCTION__+": stream header corrupted"))
      return;
   int i1=s.Unserialize_Int();
   if(!CAp::Assert(i1==m_knnfirstversion,__FUNCTION__+": stream header corrupted"))
      return;
//--- Unserialize data
   model.m_nvars=s.Unserialize_Int();
   model.m_nout=s.Unserialize_Int();
   model.m_k=s.Unserialize_Int();
   model.m_eps=s.Unserialize_Double();
   model.m_iscls=s.Unserialize_Bool();
   model.m_isdummy=s.Unserialize_Bool();
   if(!model.m_isdummy)
      CNearestNeighbor::KDTreeUnserialize(s,model.m_tree);
//--- Prepare local buffer
   KNNCreateBuffer(model,model.m_buffer);
  }
//+------------------------------------------------------------------+
//| Sets report fields to their default values                       |
//+------------------------------------------------------------------+
void CKNN::ClearReport(CKNNReport &rep)
  {
   rep.m_RelCLSError=0;
   rep.m_AvgCE=0;
   rep.m_RMSError=0;
   rep.m_AvgError=0;
   rep.m_AvgRelError=0;
  }
//+------------------------------------------------------------------+
//| This function processes buf.X and stores result to buf.Y         |
//| INPUT PARAMETERS:                                                |
//|   Model    -  KNN model                                          |
//|   Buf      -  processing buffer.                                 |
//| IMPORTANT: buffer object should be used only with model which was|
//|            used to initialize buffer. Any attempt to use buffer  |
//|            with different object is dangerous - you may get      |
//|            integrity check failure (exception) because sizes of  |
//|            internal arrays do not fit to dimensions of the model |
//|            structure.                                            |
//+------------------------------------------------------------------+
void CKNN::ProcessInternal(CKNNModel &model,CKNNBuffer &buf)
  {
//--- Quick exit if needed
   buf.m_y.Fill(0);
   if(model.m_isdummy)
      return;
//--- create variables
   int    nncnt=0;
   double v=0;
   int    nvars=model.m_nvars;
   int    nout=model.m_nout;
   bool   iscls=model.m_iscls;
//--- Perform request, average results
   nncnt=CNearestNeighbor::KDTreeTsQueryAKNN(model.m_tree,buf.m_treebuf,buf.m_x,model.m_k,true,model.m_eps);
   v=1/CApServ::Coalesce(nncnt,1);
   if(iscls)
     {
      CNearestNeighbor::KDTreeTsQueryResultsTags(model.m_tree,buf.m_treebuf,buf.m_tags);
      for(int i=0; i<nncnt; i++)
        {
         int j=buf.m_tags[i];
         buf.m_y.Add(j,v);
        }
     }
   else
     {
      CNearestNeighbor::KDTreeTsQueryResultsXY(model.m_tree,buf.m_treebuf,buf.m_xy);
      for(int i=0; i<nncnt; i++)
         for(int j=0; j<nout; j++)
            buf.m_y.Add(j,v*buf.m_xy.Get(i,nvars+j));
     }
  }
//+------------------------------------------------------------------+
