/*************************************************************************
Copyright (c) Sergey Bochkanov (ALGLIB project).

>>> SOURCE LICENSE >>>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation (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.

A copy of the GNU General Public License is available at
http://www.fsf.org/licensing/licenses
>>> END OF LICENSE >>>
*************************************************************************/
#include "stdafx.h"
#include "dataanalysis.h"

// disable some irrelevant warnings
#if (AE_COMPILER==AE_MSVC)
#pragma warning(disable:4100)
#pragma warning(disable:4127)
#pragma warning(disable:4702)
#pragma warning(disable:4996)
#endif
using namespace std;

/////////////////////////////////////////////////////////////////////////
//
// THIS SECTION CONTAINS IMPLEMENTATION OF C++ INTERFACE
//
/////////////////////////////////////////////////////////////////////////
namespace alglib
{


/*************************************************************************
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

  -- ALGLIB --
     Copyright 22.05.2008 by Bochkanov Sergey
*************************************************************************/
void dsoptimalsplit2(const real_1d_array &a, const integer_1d_array &c, const ae_int_t n, ae_int_t &info, double &threshold, double &pal, double &pbl, double &par, double &pbr, double &cve)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::dsoptimalsplit2(const_cast<alglib_impl::ae_vector*>(a.c_ptr()), const_cast<alglib_impl::ae_vector*>(c.c_ptr()), n, &info, &threshold, &pal, &pbl, &par, &pbr, &cve, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 11.12.2008 by Bochkanov Sergey
*************************************************************************/
void dsoptimalsplit2fast(real_1d_array &a, integer_1d_array &c, integer_1d_array &tiesbuf, integer_1d_array &cntbuf, real_1d_array &bufr, integer_1d_array &bufi, const ae_int_t n, const ae_int_t nc, const double alpha, ae_int_t &info, double &threshold, double &rms, double &cvrms)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::dsoptimalsplit2fast(const_cast<alglib_impl::ae_vector*>(a.c_ptr()), const_cast<alglib_impl::ae_vector*>(c.c_ptr()), const_cast<alglib_impl::ae_vector*>(tiesbuf.c_ptr()), const_cast<alglib_impl::ae_vector*>(cntbuf.c_ptr()), const_cast<alglib_impl::ae_vector*>(bufr.c_ptr()), const_cast<alglib_impl::ae_vector*>(bufi.c_ptr()), n, nc, alpha, &info, &threshold, &rms, &cvrms, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 10.07.2012 by Bochkanov Sergey
*************************************************************************/
_clusterizerstate_owner::_clusterizerstate_owner()
{
    p_struct = (alglib_impl::clusterizerstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::clusterizerstate), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_clusterizerstate_init(p_struct, NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_clusterizerstate_owner::_clusterizerstate_owner(const _clusterizerstate_owner &rhs)
{
    p_struct = (alglib_impl::clusterizerstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::clusterizerstate), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_clusterizerstate_init_copy(p_struct, const_cast<alglib_impl::clusterizerstate*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_clusterizerstate_owner& _clusterizerstate_owner::operator=(const _clusterizerstate_owner &rhs)
{
    if( this==&rhs )
        return *this;
    alglib_impl::_clusterizerstate_clear(p_struct);
    if( !alglib_impl::_clusterizerstate_init_copy(p_struct, const_cast<alglib_impl::clusterizerstate*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
    return *this;
}

_clusterizerstate_owner::~_clusterizerstate_owner()
{
    alglib_impl::_clusterizerstate_clear(p_struct);
    ae_free(p_struct);
}

alglib_impl::clusterizerstate* _clusterizerstate_owner::c_ptr()
{
    return p_struct;
}

alglib_impl::clusterizerstate* _clusterizerstate_owner::c_ptr() const
{
    return const_cast<alglib_impl::clusterizerstate*>(p_struct);
}
clusterizerstate::clusterizerstate() : _clusterizerstate_owner() 
{
}

clusterizerstate::clusterizerstate(const clusterizerstate &rhs):_clusterizerstate_owner(rhs) 
{
}

clusterizerstate& clusterizerstate::operator=(const clusterizerstate &rhs)
{
    if( this==&rhs )
        return *this;
    _clusterizerstate_owner::operator=(rhs);
    return *this;
}

clusterizerstate::~clusterizerstate()
{
}


/*************************************************************************
This structure  is used to store results of the agglomerative hierarchical
clustering (AHC).

Following information is returned:

* 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,...]).

    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.

  -- ALGLIB --
     Copyright 10.07.2012 by Bochkanov Sergey
*************************************************************************/
_ahcreport_owner::_ahcreport_owner()
{
    p_struct = (alglib_impl::ahcreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::ahcreport), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_ahcreport_init(p_struct, NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_ahcreport_owner::_ahcreport_owner(const _ahcreport_owner &rhs)
{
    p_struct = (alglib_impl::ahcreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::ahcreport), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_ahcreport_init_copy(p_struct, const_cast<alglib_impl::ahcreport*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_ahcreport_owner& _ahcreport_owner::operator=(const _ahcreport_owner &rhs)
{
    if( this==&rhs )
        return *this;
    alglib_impl::_ahcreport_clear(p_struct);
    if( !alglib_impl::_ahcreport_init_copy(p_struct, const_cast<alglib_impl::ahcreport*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
    return *this;
}

_ahcreport_owner::~_ahcreport_owner()
{
    alglib_impl::_ahcreport_clear(p_struct);
    ae_free(p_struct);
}

alglib_impl::ahcreport* _ahcreport_owner::c_ptr()
{
    return p_struct;
}

alglib_impl::ahcreport* _ahcreport_owner::c_ptr() const
{
    return const_cast<alglib_impl::ahcreport*>(p_struct);
}
ahcreport::ahcreport() : _ahcreport_owner() ,npoints(p_struct->npoints),p(&p_struct->p),z(&p_struct->z),pz(&p_struct->pz),pm(&p_struct->pm),mergedist(&p_struct->mergedist)
{
}

ahcreport::ahcreport(const ahcreport &rhs):_ahcreport_owner(rhs) ,npoints(p_struct->npoints),p(&p_struct->p),z(&p_struct->z),pz(&p_struct->pz),pm(&p_struct->pm),mergedist(&p_struct->mergedist)
{
}

ahcreport& ahcreport::operator=(const ahcreport &rhs)
{
    if( this==&rhs )
        return *this;
    _ahcreport_owner::operator=(rhs);
    return *this;
}

ahcreport::~ahcreport()
{
}


/*************************************************************************
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

  -- ALGLIB --
     Copyright 27.11.2012 by Bochkanov Sergey
*************************************************************************/
_kmeansreport_owner::_kmeansreport_owner()
{
    p_struct = (alglib_impl::kmeansreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::kmeansreport), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_kmeansreport_init(p_struct, NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_kmeansreport_owner::_kmeansreport_owner(const _kmeansreport_owner &rhs)
{
    p_struct = (alglib_impl::kmeansreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::kmeansreport), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_kmeansreport_init_copy(p_struct, const_cast<alglib_impl::kmeansreport*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_kmeansreport_owner& _kmeansreport_owner::operator=(const _kmeansreport_owner &rhs)
{
    if( this==&rhs )
        return *this;
    alglib_impl::_kmeansreport_clear(p_struct);
    if( !alglib_impl::_kmeansreport_init_copy(p_struct, const_cast<alglib_impl::kmeansreport*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
    return *this;
}

_kmeansreport_owner::~_kmeansreport_owner()
{
    alglib_impl::_kmeansreport_clear(p_struct);
    ae_free(p_struct);
}

alglib_impl::kmeansreport* _kmeansreport_owner::c_ptr()
{
    return p_struct;
}

alglib_impl::kmeansreport* _kmeansreport_owner::c_ptr() const
{
    return const_cast<alglib_impl::kmeansreport*>(p_struct);
}
kmeansreport::kmeansreport() : _kmeansreport_owner() ,npoints(p_struct->npoints),nfeatures(p_struct->nfeatures),terminationtype(p_struct->terminationtype),k(p_struct->k),c(&p_struct->c),cidx(&p_struct->cidx)
{
}

kmeansreport::kmeansreport(const kmeansreport &rhs):_kmeansreport_owner(rhs) ,npoints(p_struct->npoints),nfeatures(p_struct->nfeatures),terminationtype(p_struct->terminationtype),k(p_struct->k),c(&p_struct->c),cidx(&p_struct->cidx)
{
}

kmeansreport& kmeansreport::operator=(const kmeansreport &rhs)
{
    if( this==&rhs )
        return *this;
    _kmeansreport_owner::operator=(rhs);
    return *this;
}

kmeansreport::~kmeansreport()
{
}

/*************************************************************************
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

  -- ALGLIB --
     Copyright 10.07.2012 by Bochkanov Sergey
*************************************************************************/
void clusterizercreate(clusterizerstate &s)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::clusterizercreate(const_cast<alglib_impl::clusterizerstate*>(s.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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)
                * 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.

  -- ALGLIB --
     Copyright 10.07.2012 by Bochkanov Sergey
*************************************************************************/
void clusterizersetpoints(const clusterizerstate &s, const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nfeatures, const ae_int_t disttype)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::clusterizersetpoints(const_cast<alglib_impl::clusterizerstate*>(s.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, nfeatures, disttype, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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)
                * 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.

  -- ALGLIB --
     Copyright 10.07.2012 by Bochkanov Sergey
*************************************************************************/
void clusterizersetpoints(const clusterizerstate &s, const real_2d_array &xy, const ae_int_t disttype)
{
    alglib_impl::ae_state _alglib_env_state;    
    ae_int_t npoints;
    ae_int_t nfeatures;

    npoints = xy.rows();
    nfeatures = xy.cols();
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::clusterizersetpoints(const_cast<alglib_impl::clusterizerstate*>(s.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, nfeatures, disttype, &_alglib_env_state);

        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 10.07.2012 by Bochkanov Sergey
*************************************************************************/
void clusterizersetdistances(const clusterizerstate &s, const real_2d_array &d, const ae_int_t npoints, const bool isupper)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::clusterizersetdistances(const_cast<alglib_impl::clusterizerstate*>(s.c_ptr()), const_cast<alglib_impl::ae_matrix*>(d.c_ptr()), npoints, isupper, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 10.07.2012 by Bochkanov Sergey
*************************************************************************/
void clusterizersetdistances(const clusterizerstate &s, const real_2d_array &d, const bool isupper)
{
    alglib_impl::ae_state _alglib_env_state;    
    ae_int_t npoints;
    if( (d.rows()!=d.cols()))
        throw ap_error("Error while calling 'clusterizersetdistances': looks like one of arguments has wrong size");
    npoints = d.rows();
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::clusterizersetdistances(const_cast<alglib_impl::clusterizerstate*>(s.c_ptr()), const_cast<alglib_impl::ae_matrix*>(d.c_ptr()), npoints, isupper, &_alglib_env_state);

        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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

  -- ALGLIB --
     Copyright 10.07.2012 by Bochkanov Sergey
*************************************************************************/
void clusterizersetahcalgo(const clusterizerstate &s, const ae_int_t algo)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::clusterizersetahcalgo(const_cast<alglib_impl::clusterizerstate*>(s.c_ptr()), algo, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 10.07.2012 by Bochkanov Sergey
*************************************************************************/
void clusterizersetkmeanslimits(const clusterizerstate &s, const ae_int_t restarts, const ae_int_t maxits)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::clusterizersetkmeanslimits(const_cast<alglib_impl::clusterizerstate*>(s.c_ptr()), restarts, maxits, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
This function performs agglomerative hierarchical clustering

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.

  -- ALGLIB --
     Copyright 10.07.2012 by Bochkanov Sergey
*************************************************************************/
void clusterizerrunahc(const clusterizerstate &s, ahcreport &rep)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::clusterizerrunahc(const_cast<alglib_impl::clusterizerstate*>(s.c_ptr()), const_cast<alglib_impl::ahcreport*>(rep.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
This function performs clustering by k-means++ algorithm.

You may change algorithm properties like number of restarts or  iterations
limit by calling ClusterizerSetKMeansLimits() functions.

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).

  -- ALGLIB --
     Copyright 10.07.2012 by Bochkanov Sergey
*************************************************************************/
void clusterizerrunkmeans(const clusterizerstate &s, const ae_int_t k, kmeansreport &rep)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::clusterizerrunkmeans(const_cast<alglib_impl::clusterizerstate*>(s.c_ptr()), k, const_cast<alglib_impl::kmeansreport*>(rep.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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)
                * 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)

NOTES: 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.

  -- ALGLIB --
     Copyright 10.07.2012 by Bochkanov Sergey
*************************************************************************/
void clusterizergetdistances(const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nfeatures, const ae_int_t disttype, real_2d_array &d)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::clusterizergetdistances(const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, nfeatures, disttype, const_cast<alglib_impl::ae_matrix*>(d.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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))).

  -- ALGLIB --
     Copyright 10.07.2012 by Bochkanov Sergey
*************************************************************************/
void clusterizergetkclusters(const ahcreport &rep, const ae_int_t k, integer_1d_array &cidx, integer_1d_array &cz)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::clusterizergetkclusters(const_cast<alglib_impl::ahcreport*>(rep.c_ptr()), k, const_cast<alglib_impl::ae_vector*>(cidx.c_ptr()), const_cast<alglib_impl::ae_vector*>(cz.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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))).

  -- ALGLIB --
     Copyright 10.07.2012 by Bochkanov Sergey
*************************************************************************/
void clusterizerseparatedbydist(const ahcreport &rep, const double r, ae_int_t &k, integer_1d_array &cidx, integer_1d_array &cz)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::clusterizerseparatedbydist(const_cast<alglib_impl::ahcreport*>(rep.c_ptr()), r, &k, const_cast<alglib_impl::ae_vector*>(cidx.c_ptr()), const_cast<alglib_impl::ae_vector*>(cz.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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))).

  -- ALGLIB --
     Copyright 10.07.2012 by Bochkanov Sergey
*************************************************************************/
void clusterizerseparatedbycorr(const ahcreport &rep, const double r, ae_int_t &k, integer_1d_array &cidx, integer_1d_array &cz)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::clusterizerseparatedbycorr(const_cast<alglib_impl::ahcreport*>(rep.c_ptr()), r, &k, const_cast<alglib_impl::ae_vector*>(cidx.c_ptr()), const_cast<alglib_impl::ae_vector*>(cz.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
k-means++ clusterization.
Backward compatibility function, we recommend to use CLUSTERING subpackage
as better replacement.

  -- ALGLIB --
     Copyright 21.03.2009 by Bochkanov Sergey
*************************************************************************/
void kmeansgenerate(const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nvars, const ae_int_t k, const ae_int_t restarts, ae_int_t &info, real_2d_array &c, integer_1d_array &xyc)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::kmeansgenerate(const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, nvars, k, restarts, &info, const_cast<alglib_impl::ae_matrix*>(c.c_ptr()), const_cast<alglib_impl::ae_vector*>(xyc.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************

*************************************************************************/
_decisionforest_owner::_decisionforest_owner()
{
    p_struct = (alglib_impl::decisionforest*)alglib_impl::ae_malloc(sizeof(alglib_impl::decisionforest), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_decisionforest_init(p_struct, NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_decisionforest_owner::_decisionforest_owner(const _decisionforest_owner &rhs)
{
    p_struct = (alglib_impl::decisionforest*)alglib_impl::ae_malloc(sizeof(alglib_impl::decisionforest), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_decisionforest_init_copy(p_struct, const_cast<alglib_impl::decisionforest*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_decisionforest_owner& _decisionforest_owner::operator=(const _decisionforest_owner &rhs)
{
    if( this==&rhs )
        return *this;
    alglib_impl::_decisionforest_clear(p_struct);
    if( !alglib_impl::_decisionforest_init_copy(p_struct, const_cast<alglib_impl::decisionforest*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
    return *this;
}

_decisionforest_owner::~_decisionforest_owner()
{
    alglib_impl::_decisionforest_clear(p_struct);
    ae_free(p_struct);
}

alglib_impl::decisionforest* _decisionforest_owner::c_ptr()
{
    return p_struct;
}

alglib_impl::decisionforest* _decisionforest_owner::c_ptr() const
{
    return const_cast<alglib_impl::decisionforest*>(p_struct);
}
decisionforest::decisionforest() : _decisionforest_owner() 
{
}

decisionforest::decisionforest(const decisionforest &rhs):_decisionforest_owner(rhs) 
{
}

decisionforest& decisionforest::operator=(const decisionforest &rhs)
{
    if( this==&rhs )
        return *this;
    _decisionforest_owner::operator=(rhs);
    return *this;
}

decisionforest::~decisionforest()
{
}


/*************************************************************************

*************************************************************************/
_dfreport_owner::_dfreport_owner()
{
    p_struct = (alglib_impl::dfreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::dfreport), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_dfreport_init(p_struct, NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_dfreport_owner::_dfreport_owner(const _dfreport_owner &rhs)
{
    p_struct = (alglib_impl::dfreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::dfreport), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_dfreport_init_copy(p_struct, const_cast<alglib_impl::dfreport*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_dfreport_owner& _dfreport_owner::operator=(const _dfreport_owner &rhs)
{
    if( this==&rhs )
        return *this;
    alglib_impl::_dfreport_clear(p_struct);
    if( !alglib_impl::_dfreport_init_copy(p_struct, const_cast<alglib_impl::dfreport*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
    return *this;
}

_dfreport_owner::~_dfreport_owner()
{
    alglib_impl::_dfreport_clear(p_struct);
    ae_free(p_struct);
}

alglib_impl::dfreport* _dfreport_owner::c_ptr()
{
    return p_struct;
}

alglib_impl::dfreport* _dfreport_owner::c_ptr() const
{
    return const_cast<alglib_impl::dfreport*>(p_struct);
}
dfreport::dfreport() : _dfreport_owner() ,relclserror(p_struct->relclserror),avgce(p_struct->avgce),rmserror(p_struct->rmserror),avgerror(p_struct->avgerror),avgrelerror(p_struct->avgrelerror),oobrelclserror(p_struct->oobrelclserror),oobavgce(p_struct->oobavgce),oobrmserror(p_struct->oobrmserror),oobavgerror(p_struct->oobavgerror),oobavgrelerror(p_struct->oobavgrelerror)
{
}

dfreport::dfreport(const dfreport &rhs):_dfreport_owner(rhs) ,relclserror(p_struct->relclserror),avgce(p_struct->avgce),rmserror(p_struct->rmserror),avgerror(p_struct->avgerror),avgrelerror(p_struct->avgrelerror),oobrelclserror(p_struct->oobrelclserror),oobavgce(p_struct->oobavgce),oobrmserror(p_struct->oobrmserror),oobavgerror(p_struct->oobavgerror),oobavgrelerror(p_struct->oobavgrelerror)
{
}

dfreport& dfreport::operator=(const dfreport &rhs)
{
    if( this==&rhs )
        return *this;
    _dfreport_owner::operator=(rhs);
    return *this;
}

dfreport::~dfreport()
{
}


/*************************************************************************
This function serializes data structure to string.

Important properties of s_out:
* it contains alphanumeric characters, dots, underscores, minus signs
* these symbols are grouped into words, which are separated by spaces
  and Windows-style (CR+LF) newlines
* although  serializer  uses  spaces and CR+LF as separators, you can 
  replace any separator character by arbitrary combination of spaces,
  tabs, Windows or Unix newlines. It allows flexible reformatting  of
  the  string  in  case you want to include it into text or XML file. 
  But you should not insert separators into the middle of the "words"
  nor you should change case of letters.
* s_out can be freely moved between 32-bit and 64-bit systems, little
  and big endian machines, and so on. You can serialize structure  on
  32-bit machine and unserialize it on 64-bit one (or vice versa), or
  serialize  it  on  SPARC  and  unserialize  on  x86.  You  can also 
  serialize  it  in  C++ version of ALGLIB and unserialize in C# one, 
  and vice versa.
*************************************************************************/
void dfserialize(decisionforest &obj, std::string &s_out)
{
    alglib_impl::ae_state state;
    alglib_impl::ae_serializer serializer;
    alglib_impl::ae_int_t ssize;

    alglib_impl::ae_state_init(&state);
    try
    {
        alglib_impl::ae_serializer_init(&serializer);
        alglib_impl::ae_serializer_alloc_start(&serializer);
        alglib_impl::dfalloc(&serializer, obj.c_ptr(), &state);
        ssize = alglib_impl::ae_serializer_get_alloc_size(&serializer);
        s_out.clear();
        s_out.reserve((size_t)(ssize+1));
        alglib_impl::ae_serializer_sstart_str(&serializer, &s_out);
        alglib_impl::dfserialize(&serializer, obj.c_ptr(), &state);
        alglib_impl::ae_serializer_stop(&serializer);
        if( s_out.length()>(size_t)ssize )
            throw ap_error("ALGLIB: serialization integrity error");
        alglib_impl::ae_serializer_clear(&serializer);
        alglib_impl::ae_state_clear(&state);
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(state.error_msg);
    }
}
/*************************************************************************
This function unserializes data structure from string.
*************************************************************************/
void dfunserialize(std::string &s_in, decisionforest &obj)
{
    alglib_impl::ae_state state;
    alglib_impl::ae_serializer serializer;

    alglib_impl::ae_state_init(&state);
    try
    {
        alglib_impl::ae_serializer_init(&serializer);
        alglib_impl::ae_serializer_ustart_str(&serializer, &s_in);
        alglib_impl::dfunserialize(&serializer, obj.c_ptr(), &state);
        alglib_impl::ae_serializer_stop(&serializer);
        alglib_impl::ae_serializer_clear(&serializer);
        alglib_impl::ae_state_clear(&state);
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(state.error_msg);
    }
}

/*************************************************************************
This subroutine builds random decision forest.

INPUT PARAMETERS:
    XY          -   training set
    NPoints     -   training set size, NPoints>=1
    NVars       -   number of independent variables, NVars>=1
    NClasses    -   task type:
                    * NClasses=1 - regression task with one
                                   dependent variable
                    * NClasses>1 - classification task with
                                   NClasses classes.
    NTrees      -   number of trees in a forest, NTrees>=1.
                    recommended values: 50-100.
    R           -   percent of a training set used to build
                    individual trees. 0<R<=1.
                    recommended values: 0.1 <= R <= 0.66.

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<1, NVars<1, NClasses<1, NTrees<1, R<=0
                          or R>1).
                    *  1, if task has been solved
    DF          -   model built
    Rep         -   training report, contains error on a training set
                    and out-of-bag estimates of generalization error.

  -- ALGLIB --
     Copyright 19.02.2009 by Bochkanov Sergey
*************************************************************************/
void dfbuildrandomdecisionforest(const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nvars, const ae_int_t nclasses, const ae_int_t ntrees, const double r, ae_int_t &info, decisionforest &df, dfreport &rep)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::dfbuildrandomdecisionforest(const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, nvars, nclasses, ntrees, r, &info, const_cast<alglib_impl::decisionforest*>(df.c_ptr()), const_cast<alglib_impl::dfreport*>(rep.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
This subroutine builds random decision forest.
This function gives ability to tune number of variables used when choosing
best split.

INPUT PARAMETERS:
    XY          -   training set
    NPoints     -   training set size, NPoints>=1
    NVars       -   number of independent variables, NVars>=1
    NClasses    -   task type:
                    * NClasses=1 - regression task with one
                                   dependent variable
                    * NClasses>1 - classification task with
                                   NClasses classes.
    NTrees      -   number of trees in a forest, NTrees>=1.
                    recommended values: 50-100.
    NRndVars    -   number of variables used when choosing best split
    R           -   percent of a training set used to build
                    individual trees. 0<R<=1.
                    recommended values: 0.1 <= R <= 0.66.

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<1, NVars<1, NClasses<1, NTrees<1, R<=0
                          or R>1).
                    *  1, if task has been solved
    DF          -   model built
    Rep         -   training report, contains error on a training set
                    and out-of-bag estimates of generalization error.

  -- ALGLIB --
     Copyright 19.02.2009 by Bochkanov Sergey
*************************************************************************/
void dfbuildrandomdecisionforestx1(const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nvars, const ae_int_t nclasses, const ae_int_t ntrees, const ae_int_t nrndvars, const double r, ae_int_t &info, decisionforest &df, dfreport &rep)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::dfbuildrandomdecisionforestx1(const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, nvars, nclasses, ntrees, nrndvars, r, &info, const_cast<alglib_impl::decisionforest*>(df.c_ptr()), const_cast<alglib_impl::dfreport*>(rep.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 16.02.2009 by Bochkanov Sergey
*************************************************************************/
void dfprocess(const decisionforest &df, const real_1d_array &x, real_1d_array &y)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::dfprocess(const_cast<alglib_impl::decisionforest*>(df.c_ptr()), const_cast<alglib_impl::ae_vector*>(x.c_ptr()), const_cast<alglib_impl::ae_vector*>(y.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
'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.

  -- ALGLIB --
     Copyright 28.02.2010 by Bochkanov Sergey
*************************************************************************/
void dfprocessi(const decisionforest &df, const real_1d_array &x, real_1d_array &y)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::dfprocessi(const_cast<alglib_impl::decisionforest*>(df.c_ptr()), const_cast<alglib_impl::ae_vector*>(x.c_ptr()), const_cast<alglib_impl::ae_vector*>(y.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 16.02.2009 by Bochkanov Sergey
*************************************************************************/
double dfrelclserror(const decisionforest &df, const real_2d_array &xy, const ae_int_t npoints)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::dfrelclserror(const_cast<alglib_impl::decisionforest*>(df.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 16.02.2009 by Bochkanov Sergey
*************************************************************************/
double dfavgce(const decisionforest &df, const real_2d_array &xy, const ae_int_t npoints)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::dfavgce(const_cast<alglib_impl::decisionforest*>(df.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 16.02.2009 by Bochkanov Sergey
*************************************************************************/
double dfrmserror(const decisionforest &df, const real_2d_array &xy, const ae_int_t npoints)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::dfrmserror(const_cast<alglib_impl::decisionforest*>(df.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 16.02.2009 by Bochkanov Sergey
*************************************************************************/
double dfavgerror(const decisionforest &df, const real_2d_array &xy, const ae_int_t npoints)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::dfavgerror(const_cast<alglib_impl::decisionforest*>(df.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 16.02.2009 by Bochkanov Sergey
*************************************************************************/
double dfavgrelerror(const decisionforest &df, const real_2d_array &xy, const ae_int_t npoints)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::dfavgrelerror(const_cast<alglib_impl::decisionforest*>(df.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************

*************************************************************************/
_linearmodel_owner::_linearmodel_owner()
{
    p_struct = (alglib_impl::linearmodel*)alglib_impl::ae_malloc(sizeof(alglib_impl::linearmodel), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_linearmodel_init(p_struct, NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_linearmodel_owner::_linearmodel_owner(const _linearmodel_owner &rhs)
{
    p_struct = (alglib_impl::linearmodel*)alglib_impl::ae_malloc(sizeof(alglib_impl::linearmodel), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_linearmodel_init_copy(p_struct, const_cast<alglib_impl::linearmodel*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_linearmodel_owner& _linearmodel_owner::operator=(const _linearmodel_owner &rhs)
{
    if( this==&rhs )
        return *this;
    alglib_impl::_linearmodel_clear(p_struct);
    if( !alglib_impl::_linearmodel_init_copy(p_struct, const_cast<alglib_impl::linearmodel*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
    return *this;
}

_linearmodel_owner::~_linearmodel_owner()
{
    alglib_impl::_linearmodel_clear(p_struct);
    ae_free(p_struct);
}

alglib_impl::linearmodel* _linearmodel_owner::c_ptr()
{
    return p_struct;
}

alglib_impl::linearmodel* _linearmodel_owner::c_ptr() const
{
    return const_cast<alglib_impl::linearmodel*>(p_struct);
}
linearmodel::linearmodel() : _linearmodel_owner() 
{
}

linearmodel::linearmodel(const linearmodel &rhs):_linearmodel_owner(rhs) 
{
}

linearmodel& linearmodel::operator=(const linearmodel &rhs)
{
    if( this==&rhs )
        return *this;
    _linearmodel_owner::operator=(rhs);
    return *this;
}

linearmodel::~linearmodel()
{
}


/*************************************************************************
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.
*************************************************************************/
_lrreport_owner::_lrreport_owner()
{
    p_struct = (alglib_impl::lrreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::lrreport), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_lrreport_init(p_struct, NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_lrreport_owner::_lrreport_owner(const _lrreport_owner &rhs)
{
    p_struct = (alglib_impl::lrreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::lrreport), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_lrreport_init_copy(p_struct, const_cast<alglib_impl::lrreport*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_lrreport_owner& _lrreport_owner::operator=(const _lrreport_owner &rhs)
{
    if( this==&rhs )
        return *this;
    alglib_impl::_lrreport_clear(p_struct);
    if( !alglib_impl::_lrreport_init_copy(p_struct, const_cast<alglib_impl::lrreport*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
    return *this;
}

_lrreport_owner::~_lrreport_owner()
{
    alglib_impl::_lrreport_clear(p_struct);
    ae_free(p_struct);
}

alglib_impl::lrreport* _lrreport_owner::c_ptr()
{
    return p_struct;
}

alglib_impl::lrreport* _lrreport_owner::c_ptr() const
{
    return const_cast<alglib_impl::lrreport*>(p_struct);
}
lrreport::lrreport() : _lrreport_owner() ,c(&p_struct->c),rmserror(p_struct->rmserror),avgerror(p_struct->avgerror),avgrelerror(p_struct->avgrelerror),cvrmserror(p_struct->cvrmserror),cvavgerror(p_struct->cvavgerror),cvavgrelerror(p_struct->cvavgrelerror),ncvdefects(p_struct->ncvdefects),cvdefects(&p_struct->cvdefects)
{
}

lrreport::lrreport(const lrreport &rhs):_lrreport_owner(rhs) ,c(&p_struct->c),rmserror(p_struct->rmserror),avgerror(p_struct->avgerror),avgrelerror(p_struct->avgrelerror),cvrmserror(p_struct->cvrmserror),cvavgerror(p_struct->cvavgerror),cvavgrelerror(p_struct->cvavgrelerror),ncvdefects(p_struct->ncvdefects),cvdefects(&p_struct->cvdefects)
{
}

lrreport& lrreport::operator=(const lrreport &rhs)
{
    if( this==&rhs )
        return *this;
    _lrreport_owner::operator=(rhs);
    return *this;
}

lrreport::~lrreport()
{
}

/*************************************************************************
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


  -- ALGLIB --
     Copyright 02.08.2008 by Bochkanov Sergey
*************************************************************************/
void lrbuild(const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nvars, ae_int_t &info, linearmodel &lm, lrreport &ar)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::lrbuild(const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, nvars, &info, const_cast<alglib_impl::linearmodel*>(lm.c_ptr()), const_cast<alglib_impl::lrreport*>(ar.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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


  -- ALGLIB --
     Copyright 02.08.2008 by Bochkanov Sergey
*************************************************************************/
void lrbuilds(const real_2d_array &xy, const real_1d_array &s, const ae_int_t npoints, const ae_int_t nvars, ae_int_t &info, linearmodel &lm, lrreport &ar)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::lrbuilds(const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), const_cast<alglib_impl::ae_vector*>(s.c_ptr()), npoints, nvars, &info, const_cast<alglib_impl::linearmodel*>(lm.c_ptr()), const_cast<alglib_impl::lrreport*>(ar.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Like LRBuildS, but builds model

    Y = A(0)*X[0] + ... + A(N-1)*X[N-1]

i.e. with zero constant term.

  -- ALGLIB --
     Copyright 30.10.2008 by Bochkanov Sergey
*************************************************************************/
void lrbuildzs(const real_2d_array &xy, const real_1d_array &s, const ae_int_t npoints, const ae_int_t nvars, ae_int_t &info, linearmodel &lm, lrreport &ar)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::lrbuildzs(const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), const_cast<alglib_impl::ae_vector*>(s.c_ptr()), npoints, nvars, &info, const_cast<alglib_impl::linearmodel*>(lm.c_ptr()), const_cast<alglib_impl::lrreport*>(ar.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Like LRBuild but builds model

    Y = A(0)*X[0] + ... + A(N-1)*X[N-1]

i.e. with zero constant term.

  -- ALGLIB --
     Copyright 30.10.2008 by Bochkanov Sergey
*************************************************************************/
void lrbuildz(const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nvars, ae_int_t &info, linearmodel &lm, lrreport &ar)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::lrbuildz(const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, nvars, &info, const_cast<alglib_impl::linearmodel*>(lm.c_ptr()), const_cast<alglib_impl::lrreport*>(ar.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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)

  -- ALGLIB --
     Copyright 30.08.2008 by Bochkanov Sergey
*************************************************************************/
void lrunpack(const linearmodel &lm, real_1d_array &v, ae_int_t &nvars)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::lrunpack(const_cast<alglib_impl::linearmodel*>(lm.c_ptr()), const_cast<alglib_impl::ae_vector*>(v.c_ptr()), &nvars, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
"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.

  -- ALGLIB --
     Copyright 30.08.2008 by Bochkanov Sergey
*************************************************************************/
void lrpack(const real_1d_array &v, const ae_int_t nvars, linearmodel &lm)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::lrpack(const_cast<alglib_impl::ae_vector*>(v.c_ptr()), nvars, const_cast<alglib_impl::linearmodel*>(lm.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Procesing

INPUT PARAMETERS:
    LM      -   linear model
    X       -   input vector,  array[0..NVars-1].

Result:
    value of linear model regression estimate

  -- ALGLIB --
     Copyright 03.09.2008 by Bochkanov Sergey
*************************************************************************/
double lrprocess(const linearmodel &lm, const real_1d_array &x)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::lrprocess(const_cast<alglib_impl::linearmodel*>(lm.c_ptr()), const_cast<alglib_impl::ae_vector*>(x.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
RMS error on the test set

INPUT PARAMETERS:
    LM      -   linear model
    XY      -   test set
    NPoints -   test set size

RESULT:
    root mean square error.

  -- ALGLIB --
     Copyright 30.08.2008 by Bochkanov Sergey
*************************************************************************/
double lrrmserror(const linearmodel &lm, const real_2d_array &xy, const ae_int_t npoints)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::lrrmserror(const_cast<alglib_impl::linearmodel*>(lm.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Average error on the test set

INPUT PARAMETERS:
    LM      -   linear model
    XY      -   test set
    NPoints -   test set size

RESULT:
    average error.

  -- ALGLIB --
     Copyright 30.08.2008 by Bochkanov Sergey
*************************************************************************/
double lravgerror(const linearmodel &lm, const real_2d_array &xy, const ae_int_t npoints)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::lravgerror(const_cast<alglib_impl::linearmodel*>(lm.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
RMS error on the test set

INPUT PARAMETERS:
    LM      -   linear model
    XY      -   test set
    NPoints -   test set size

RESULT:
    average relative error.

  -- ALGLIB --
     Copyright 30.08.2008 by Bochkanov Sergey
*************************************************************************/
double lravgrelerror(const linearmodel &lm, const real_2d_array &xy, const ae_int_t npoints)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::lravgrelerror(const_cast<alglib_impl::linearmodel*>(lm.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 25.10.2011 by Bochkanov Sergey
*************************************************************************/
void filtersma(real_1d_array &x, const ae_int_t n, const ae_int_t k)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::filtersma(const_cast<alglib_impl::ae_vector*>(x.c_ptr()), n, k, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 25.10.2011 by Bochkanov Sergey
*************************************************************************/
void filtersma(real_1d_array &x, const ae_int_t k)
{
    alglib_impl::ae_state _alglib_env_state;    
    ae_int_t n;

    n = x.length();
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::filtersma(const_cast<alglib_impl::ae_vector*>(x.c_ptr()), n, k, &_alglib_env_state);

        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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).

  -- ALGLIB --
     Copyright 25.10.2011 by Bochkanov Sergey
*************************************************************************/
void filterema(real_1d_array &x, const ae_int_t n, const double alpha)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::filterema(const_cast<alglib_impl::ae_vector*>(x.c_ptr()), n, alpha, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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).

  -- ALGLIB --
     Copyright 25.10.2011 by Bochkanov Sergey
*************************************************************************/
void filterema(real_1d_array &x, const double alpha)
{
    alglib_impl::ae_state _alglib_env_state;    
    ae_int_t n;

    n = x.length();
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::filterema(const_cast<alglib_impl::ae_vector*>(x.c_ptr()), n, alpha, &_alglib_env_state);

        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 25.10.2011 by Bochkanov Sergey
*************************************************************************/
void filterlrma(real_1d_array &x, const ae_int_t n, const ae_int_t k)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::filterlrma(const_cast<alglib_impl::ae_vector*>(x.c_ptr()), n, k, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 25.10.2011 by Bochkanov Sergey
*************************************************************************/
void filterlrma(real_1d_array &x, const ae_int_t k)
{
    alglib_impl::ae_state _alglib_env_state;    
    ae_int_t n;

    n = x.length();
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::filterlrma(const_cast<alglib_impl::ae_vector*>(x.c_ptr()), n, k, &_alglib_env_state);

        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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]

  -- ALGLIB --
     Copyright 31.05.2008 by Bochkanov Sergey
*************************************************************************/
void fisherlda(const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nvars, const ae_int_t nclasses, ae_int_t &info, real_1d_array &w)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::fisherlda(const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, nvars, nclasses, &info, const_cast<alglib_impl::ae_vector*>(w.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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)

  -- ALGLIB --
     Copyright 31.05.2008 by Bochkanov Sergey
*************************************************************************/
void fisherldan(const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nvars, const ae_int_t nclasses, ae_int_t &info, real_2d_array &w)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::fisherldan(const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, nvars, nclasses, &info, const_cast<alglib_impl::ae_matrix*>(w.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************

*************************************************************************/
_multilayerperceptron_owner::_multilayerperceptron_owner()
{
    p_struct = (alglib_impl::multilayerperceptron*)alglib_impl::ae_malloc(sizeof(alglib_impl::multilayerperceptron), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_multilayerperceptron_init(p_struct, NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_multilayerperceptron_owner::_multilayerperceptron_owner(const _multilayerperceptron_owner &rhs)
{
    p_struct = (alglib_impl::multilayerperceptron*)alglib_impl::ae_malloc(sizeof(alglib_impl::multilayerperceptron), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_multilayerperceptron_init_copy(p_struct, const_cast<alglib_impl::multilayerperceptron*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_multilayerperceptron_owner& _multilayerperceptron_owner::operator=(const _multilayerperceptron_owner &rhs)
{
    if( this==&rhs )
        return *this;
    alglib_impl::_multilayerperceptron_clear(p_struct);
    if( !alglib_impl::_multilayerperceptron_init_copy(p_struct, const_cast<alglib_impl::multilayerperceptron*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
    return *this;
}

_multilayerperceptron_owner::~_multilayerperceptron_owner()
{
    alglib_impl::_multilayerperceptron_clear(p_struct);
    ae_free(p_struct);
}

alglib_impl::multilayerperceptron* _multilayerperceptron_owner::c_ptr()
{
    return p_struct;
}

alglib_impl::multilayerperceptron* _multilayerperceptron_owner::c_ptr() const
{
    return const_cast<alglib_impl::multilayerperceptron*>(p_struct);
}
multilayerperceptron::multilayerperceptron() : _multilayerperceptron_owner() 
{
}

multilayerperceptron::multilayerperceptron(const multilayerperceptron &rhs):_multilayerperceptron_owner(rhs) 
{
}

multilayerperceptron& multilayerperceptron::operator=(const multilayerperceptron &rhs)
{
    if( this==&rhs )
        return *this;
    _multilayerperceptron_owner::operator=(rhs);
    return *this;
}

multilayerperceptron::~multilayerperceptron()
{
}


/*************************************************************************
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
*************************************************************************/
_modelerrors_owner::_modelerrors_owner()
{
    p_struct = (alglib_impl::modelerrors*)alglib_impl::ae_malloc(sizeof(alglib_impl::modelerrors), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_modelerrors_init(p_struct, NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_modelerrors_owner::_modelerrors_owner(const _modelerrors_owner &rhs)
{
    p_struct = (alglib_impl::modelerrors*)alglib_impl::ae_malloc(sizeof(alglib_impl::modelerrors), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_modelerrors_init_copy(p_struct, const_cast<alglib_impl::modelerrors*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_modelerrors_owner& _modelerrors_owner::operator=(const _modelerrors_owner &rhs)
{
    if( this==&rhs )
        return *this;
    alglib_impl::_modelerrors_clear(p_struct);
    if( !alglib_impl::_modelerrors_init_copy(p_struct, const_cast<alglib_impl::modelerrors*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
    return *this;
}

_modelerrors_owner::~_modelerrors_owner()
{
    alglib_impl::_modelerrors_clear(p_struct);
    ae_free(p_struct);
}

alglib_impl::modelerrors* _modelerrors_owner::c_ptr()
{
    return p_struct;
}

alglib_impl::modelerrors* _modelerrors_owner::c_ptr() const
{
    return const_cast<alglib_impl::modelerrors*>(p_struct);
}
modelerrors::modelerrors() : _modelerrors_owner() ,relclserror(p_struct->relclserror),avgce(p_struct->avgce),rmserror(p_struct->rmserror),avgerror(p_struct->avgerror),avgrelerror(p_struct->avgrelerror)
{
}

modelerrors::modelerrors(const modelerrors &rhs):_modelerrors_owner(rhs) ,relclserror(p_struct->relclserror),avgce(p_struct->avgce),rmserror(p_struct->rmserror),avgerror(p_struct->avgerror),avgrelerror(p_struct->avgrelerror)
{
}

modelerrors& modelerrors::operator=(const modelerrors &rhs)
{
    if( this==&rhs )
        return *this;
    _modelerrors_owner::operator=(rhs);
    return *this;
}

modelerrors::~modelerrors()
{
}


/*************************************************************************
This function serializes data structure to string.

Important properties of s_out:
* it contains alphanumeric characters, dots, underscores, minus signs
* these symbols are grouped into words, which are separated by spaces
  and Windows-style (CR+LF) newlines
* although  serializer  uses  spaces and CR+LF as separators, you can 
  replace any separator character by arbitrary combination of spaces,
  tabs, Windows or Unix newlines. It allows flexible reformatting  of
  the  string  in  case you want to include it into text or XML file. 
  But you should not insert separators into the middle of the "words"
  nor you should change case of letters.
* s_out can be freely moved between 32-bit and 64-bit systems, little
  and big endian machines, and so on. You can serialize structure  on
  32-bit machine and unserialize it on 64-bit one (or vice versa), or
  serialize  it  on  SPARC  and  unserialize  on  x86.  You  can also 
  serialize  it  in  C++ version of ALGLIB and unserialize in C# one, 
  and vice versa.
*************************************************************************/
void mlpserialize(multilayerperceptron &obj, std::string &s_out)
{
    alglib_impl::ae_state state;
    alglib_impl::ae_serializer serializer;
    alglib_impl::ae_int_t ssize;

    alglib_impl::ae_state_init(&state);
    try
    {
        alglib_impl::ae_serializer_init(&serializer);
        alglib_impl::ae_serializer_alloc_start(&serializer);
        alglib_impl::mlpalloc(&serializer, obj.c_ptr(), &state);
        ssize = alglib_impl::ae_serializer_get_alloc_size(&serializer);
        s_out.clear();
        s_out.reserve((size_t)(ssize+1));
        alglib_impl::ae_serializer_sstart_str(&serializer, &s_out);
        alglib_impl::mlpserialize(&serializer, obj.c_ptr(), &state);
        alglib_impl::ae_serializer_stop(&serializer);
        if( s_out.length()>(size_t)ssize )
            throw ap_error("ALGLIB: serialization integrity error");
        alglib_impl::ae_serializer_clear(&serializer);
        alglib_impl::ae_state_clear(&state);
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(state.error_msg);
    }
}
/*************************************************************************
This function unserializes data structure from string.
*************************************************************************/
void mlpunserialize(std::string &s_in, multilayerperceptron &obj)
{
    alglib_impl::ae_state state;
    alglib_impl::ae_serializer serializer;

    alglib_impl::ae_state_init(&state);
    try
    {
        alglib_impl::ae_serializer_init(&serializer);
        alglib_impl::ae_serializer_ustart_str(&serializer, &s_in);
        alglib_impl::mlpunserialize(&serializer, obj.c_ptr(), &state);
        alglib_impl::ae_serializer_stop(&serializer);
        alglib_impl::ae_serializer_clear(&serializer);
        alglib_impl::ae_state_clear(&state);
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(state.error_msg);
    }
}

/*************************************************************************
Creates  neural  network  with  NIn  inputs,  NOut outputs, without hidden
layers, with linear output layer. Network weights are  filled  with  small
random values.

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
void mlpcreate0(const ae_int_t nin, const ae_int_t nout, multilayerperceptron &network)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpcreate0(nin, nout, const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Same  as  MLPCreate0,  but  with  one  hidden  layer  (NHid  neurons) with
non-linear activation function. Output layer is linear.

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
void mlpcreate1(const ae_int_t nin, const ae_int_t nhid, const ae_int_t nout, multilayerperceptron &network)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpcreate1(nin, nhid, nout, const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Same as MLPCreate0, but with two hidden layers (NHid1 and  NHid2  neurons)
with non-linear activation function. Output layer is linear.
 $ALL

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
void mlpcreate2(const ae_int_t nin, const ae_int_t nhid1, const ae_int_t nhid2, const ae_int_t nout, multilayerperceptron &network)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpcreate2(nin, nhid1, nhid2, nout, const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.


  -- ALGLIB --
     Copyright 30.03.2008 by Bochkanov Sergey
*************************************************************************/
void mlpcreateb0(const ae_int_t nin, const ae_int_t nout, const double b, const double d, multilayerperceptron &network)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpcreateb0(nin, nout, b, d, const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Same as MLPCreateB0 but with non-linear hidden layer.

  -- ALGLIB --
     Copyright 30.03.2008 by Bochkanov Sergey
*************************************************************************/
void mlpcreateb1(const ae_int_t nin, const ae_int_t nhid, const ae_int_t nout, const double b, const double d, multilayerperceptron &network)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpcreateb1(nin, nhid, nout, b, d, const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Same as MLPCreateB0 but with two non-linear hidden layers.

  -- ALGLIB --
     Copyright 30.03.2008 by Bochkanov Sergey
*************************************************************************/
void mlpcreateb2(const ae_int_t nin, const ae_int_t nhid1, const ae_int_t nhid2, const ae_int_t nout, const double b, const double d, multilayerperceptron &network)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpcreateb2(nin, nhid1, nhid2, nout, b, d, const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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].

  -- ALGLIB --
     Copyright 30.03.2008 by Bochkanov Sergey
*************************************************************************/
void mlpcreater0(const ae_int_t nin, const ae_int_t nout, const double a, const double b, multilayerperceptron &network)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpcreater0(nin, nout, a, b, const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Same as MLPCreateR0, but with non-linear hidden layer.

  -- ALGLIB --
     Copyright 30.03.2008 by Bochkanov Sergey
*************************************************************************/
void mlpcreater1(const ae_int_t nin, const ae_int_t nhid, const ae_int_t nout, const double a, const double b, multilayerperceptron &network)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpcreater1(nin, nhid, nout, a, b, const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Same as MLPCreateR0, but with two non-linear hidden layers.

  -- ALGLIB --
     Copyright 30.03.2008 by Bochkanov Sergey
*************************************************************************/
void mlpcreater2(const ae_int_t nin, const ae_int_t nhid1, const ae_int_t nhid2, const ae_int_t nout, const double a, const double b, multilayerperceptron &network)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpcreater2(nin, nhid1, nhid2, nout, a, b, const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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).

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
void mlpcreatec0(const ae_int_t nin, const ae_int_t nout, multilayerperceptron &network)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpcreatec0(nin, nout, const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Same as MLPCreateC0, but with one non-linear hidden layer.

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
void mlpcreatec1(const ae_int_t nin, const ae_int_t nhid, const ae_int_t nout, multilayerperceptron &network)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpcreatec1(nin, nhid, nout, const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Same as MLPCreateC0, but with two non-linear hidden layers.

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
void mlpcreatec2(const ae_int_t nin, const ae_int_t nhid1, const ae_int_t nhid2, const ae_int_t nout, multilayerperceptron &network)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpcreatec2(nin, nhid1, nhid2, nout, const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Randomization of neural network weights

  -- ALGLIB --
     Copyright 06.11.2007 by Bochkanov Sergey
*************************************************************************/
void mlprandomize(const multilayerperceptron &network)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlprandomize(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Randomization of neural network weights and standartisator

  -- ALGLIB --
     Copyright 10.03.2008 by Bochkanov Sergey
*************************************************************************/
void mlprandomizefull(const multilayerperceptron &network)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlprandomizefull(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Returns information about initialized network: number of inputs, outputs,
weights.

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
void mlpproperties(const multilayerperceptron &network, ae_int_t &nin, ae_int_t &nout, ae_int_t &wcount)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpproperties(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), &nin, &nout, &wcount, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Returns number of inputs.

  -- ALGLIB --
     Copyright 19.10.2011 by Bochkanov Sergey
*************************************************************************/
ae_int_t mlpgetinputscount(const multilayerperceptron &network)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::ae_int_t result = alglib_impl::mlpgetinputscount(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<ae_int_t*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Returns number of outputs.

  -- ALGLIB --
     Copyright 19.10.2011 by Bochkanov Sergey
*************************************************************************/
ae_int_t mlpgetoutputscount(const multilayerperceptron &network)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::ae_int_t result = alglib_impl::mlpgetoutputscount(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<ae_int_t*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Returns number of weights.

  -- ALGLIB --
     Copyright 19.10.2011 by Bochkanov Sergey
*************************************************************************/
ae_int_t mlpgetweightscount(const multilayerperceptron &network)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::ae_int_t result = alglib_impl::mlpgetweightscount(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<ae_int_t*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Tells whether network is SOFTMAX-normalized (i.e. classifier) or not.

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
bool mlpissoftmax(const multilayerperceptron &network)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        ae_bool result = alglib_impl::mlpissoftmax(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<bool*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
This function returns total number of layers (including input, hidden and
output layers).

  -- ALGLIB --
     Copyright 25.03.2011 by Bochkanov Sergey
*************************************************************************/
ae_int_t mlpgetlayerscount(const multilayerperceptron &network)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::ae_int_t result = alglib_impl::mlpgetlayerscount(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<ae_int_t*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 25.03.2011 by Bochkanov Sergey
*************************************************************************/
ae_int_t mlpgetlayersize(const multilayerperceptron &network, const ae_int_t k)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::ae_int_t result = alglib_impl::mlpgetlayersize(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), k, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<ae_int_t*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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

  -- ALGLIB --
     Copyright 25.03.2011 by Bochkanov Sergey
*************************************************************************/
void mlpgetinputscaling(const multilayerperceptron &network, const ae_int_t i, double &mean, double &sigma)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpgetinputscaling(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), i, &mean, &sigma, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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).

  -- ALGLIB --
     Copyright 25.03.2011 by Bochkanov Sergey
*************************************************************************/
void mlpgetoutputscaling(const multilayerperceptron &network, const ae_int_t i, double &mean, double &sigma)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpgetoutputscaling(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), i, &mean, &sigma, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 25.03.2011 by Bochkanov Sergey
*************************************************************************/
void mlpgetneuroninfo(const multilayerperceptron &network, const ae_int_t k, const ae_int_t i, ae_int_t &fkind, double &threshold)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpgetneuroninfo(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), k, i, &fkind, &threshold, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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

  -- ALGLIB --
     Copyright 25.03.2011 by Bochkanov Sergey
*************************************************************************/
double mlpgetweight(const multilayerperceptron &network, const ae_int_t k0, const ae_int_t i0, const ae_int_t k1, const ae_int_t i1)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::mlpgetweight(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), k0, i0, k1, i1, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 25.03.2011 by Bochkanov Sergey
*************************************************************************/
void mlpsetinputscaling(const multilayerperceptron &network, const ae_int_t i, const double mean, const double sigma)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpsetinputscaling(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), i, mean, sigma, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 25.03.2011 by Bochkanov Sergey
*************************************************************************/
void mlpsetoutputscaling(const multilayerperceptron &network, const ae_int_t i, const double mean, const double sigma)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpsetoutputscaling(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), i, mean, sigma, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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).

  -- ALGLIB --
     Copyright 25.03.2011 by Bochkanov Sergey
*************************************************************************/
void mlpsetneuroninfo(const multilayerperceptron &network, const ae_int_t k, const ae_int_t i, const ae_int_t fkind, const double threshold)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpsetneuroninfo(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), k, i, fkind, threshold, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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

  -- ALGLIB --
     Copyright 25.03.2011 by Bochkanov Sergey
*************************************************************************/
void mlpsetweight(const multilayerperceptron &network, const ae_int_t k0, const ae_int_t i0, const ae_int_t k1, const ae_int_t i1, const double w)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpsetweight(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), k0, i0, k1, i1, w, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
void mlpactivationfunction(const double net, const ae_int_t k, double &f, double &df, double &d2f)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpactivationfunction(net, k, &f, &df, &d2f, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
void mlpprocess(const multilayerperceptron &network, const real_1d_array &x, real_1d_array &y)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpprocess(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::ae_vector*>(x.c_ptr()), const_cast<alglib_impl::ae_vector*>(y.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
'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.

  -- ALGLIB --
     Copyright 21.09.2010 by Bochkanov Sergey
*************************************************************************/
void mlpprocessi(const multilayerperceptron &network, const real_1d_array &x, real_1d_array &y)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpprocessi(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::ae_vector*>(x.c_ptr()), const_cast<alglib_impl::ae_vector*>(y.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Error of the neural network on dataset.

INPUT PARAMETERS:
    Network     -   neural network;
    XY          -   training  set,  see  below  for  information  on   the
                    training set format;
    SSize       -   points count.

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).

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
double mlperror(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t ssize)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::mlperror(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), ssize, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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).

  -- ALGLIB --
     Copyright 23.07.2012 by Bochkanov Sergey
*************************************************************************/
double mlperrorsparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t npoints)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::mlperrorsparse(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::sparsematrix*>(xy.c_ptr()), npoints, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Natural error function for neural network, internal subroutine.

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
double mlperrorn(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t ssize)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::mlperrorn(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), ssize, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Classification error

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
ae_int_t mlpclserror(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t ssize)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::ae_int_t result = alglib_impl::mlpclserror(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), ssize, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<ae_int_t*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Relative classification error on the test set.

INPUT PARAMETERS:
    Network     -   neural network;
    XY          -   training  set,  see  below  for  information  on   the
                    training set format;
    NPoints     -   points count.

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).

  -- ALGLIB --
     Copyright 25.12.2008 by Bochkanov Sergey
*************************************************************************/
double mlprelclserror(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::mlprelclserror(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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. 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:
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).

  -- ALGLIB --
     Copyright 09.08.2012 by Bochkanov Sergey
*************************************************************************/
double mlprelclserrorsparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t npoints)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::mlprelclserrorsparse(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::sparsematrix*>(xy.c_ptr()), npoints, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Average  cross-entropy  (in bits  per element)  on the  test set.

INPUT PARAMETERS:
    Network     -   neural network;
    XY          -   training  set,  see  below  for  information  on   the
                    training set format;
    NPoints     -   points count.

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).

  -- ALGLIB --
     Copyright 08.01.2009 by Bochkanov Sergey
*************************************************************************/
double mlpavgce(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::mlpavgce(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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).

  -- ALGLIB --
     Copyright 9.08.2012 by Bochkanov Sergey
*************************************************************************/
double mlpavgcesparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t npoints)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::mlpavgcesparse(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::sparsematrix*>(xy.c_ptr()), npoints, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
RMS error on the test set given.

INPUT PARAMETERS:
    Network     -   neural network;
    XY          -   training  set,  see  below  for  information  on   the
                    training set format;
    NPoints     -   points count.

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).

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
double mlprmserror(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::mlprmserror(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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).

  -- ALGLIB --
     Copyright 09.08.2012 by Bochkanov Sergey
*************************************************************************/
double mlprmserrorsparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t npoints)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::mlprmserrorsparse(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::sparsematrix*>(xy.c_ptr()), npoints, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Average error on the test set.

INPUT PARAMETERS:
    Network     -   neural network;
    XY          -   training  set,  see  below  for  information  on   the
                    training set format;
    NPoints     -   points count.

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).

  -- ALGLIB --
     Copyright 11.03.2008 by Bochkanov Sergey
*************************************************************************/
double mlpavgerror(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::mlpavgerror(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Average 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).

  -- ALGLIB --
     Copyright 09.08.2012 by Bochkanov Sergey
*************************************************************************/
double mlpavgerrorsparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t npoints)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::mlpavgerrorsparse(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::sparsematrix*>(xy.c_ptr()), npoints, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Average relative error on the test set.

INPUT PARAMETERS:
    Network     -   neural network;
    XY          -   training  set,  see  below  for  information  on   the
                    training set format;
    NPoints     -   points count.

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).

  -- ALGLIB --
     Copyright 11.03.2008 by Bochkanov Sergey
*************************************************************************/
double mlpavgrelerror(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::mlpavgrelerror(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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).

  -- ALGLIB --
     Copyright 09.08.2012 by Bochkanov Sergey
*************************************************************************/
double mlpavgrelerrorsparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t npoints)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::mlpavgrelerrorsparse(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::sparsematrix*>(xy.c_ptr()), npoints, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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]

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
void mlpgrad(const multilayerperceptron &network, const real_1d_array &x, const real_1d_array &desiredy, double &e, real_1d_array &grad)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpgrad(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::ae_vector*>(x.c_ptr()), const_cast<alglib_impl::ae_vector*>(desiredy.c_ptr()), &e, const_cast<alglib_impl::ae_vector*>(grad.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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]

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
void mlpgradn(const multilayerperceptron &network, const real_1d_array &x, const real_1d_array &desiredy, double &e, real_1d_array &grad)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpgradn(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::ae_vector*>(x.c_ptr()), const_cast<alglib_impl::ae_vector*>(desiredy.c_ptr()), &e, const_cast<alglib_impl::ae_vector*>(grad.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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]

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
void mlpgradbatch(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t ssize, double &e, real_1d_array &grad)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpgradbatch(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), ssize, &e, const_cast<alglib_impl::ae_vector*>(grad.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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      -   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]

  -- ALGLIB --
     Copyright 26.07.2012 by Bochkanov Sergey
*************************************************************************/
void mlpgradbatchsparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t ssize, double &e, real_1d_array &grad)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpgradbatchsparse(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::sparsematrix*>(xy.c_ptr()), ssize, &e, const_cast<alglib_impl::ae_vector*>(grad.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Batch gradient calculation for a 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;
    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.
    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 MLPGradBatch function.

  -- ALGLIB --
     Copyright 26.07.2012 by Bochkanov Sergey
*************************************************************************/
void mlpgradbatchsubset(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t setsize, const integer_1d_array &idx, const ae_int_t subsetsize, double &e, real_1d_array &grad)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpgradbatchsubset(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), setsize, const_cast<alglib_impl::ae_vector*>(idx.c_ptr()), subsetsize, &e, const_cast<alglib_impl::ae_vector*>(grad.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Batch gradient calculation for a set of inputs/outputs  for  a  subset  of
dataset given by boolean mask.

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.
    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.
    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.

  -- ALGLIB --
     Copyright 26.07.2012 by Bochkanov Sergey
*************************************************************************/
void mlpgradbatchsparsesubset(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t setsize, const integer_1d_array &idx, const ae_int_t subsetsize, double &e, real_1d_array &grad)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpgradbatchsparsesubset(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::sparsematrix*>(xy.c_ptr()), setsize, const_cast<alglib_impl::ae_vector*>(idx.c_ptr()), subsetsize, &e, const_cast<alglib_impl::ae_vector*>(grad.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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]

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
void mlpgradnbatch(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t ssize, double &e, real_1d_array &grad)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpgradnbatch(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), ssize, &e, const_cast<alglib_impl::ae_vector*>(grad.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Batch Hessian calculation (natural error function) using R-algorithm.
Internal subroutine.

  -- ALGLIB --
     Copyright 26.01.2008 by Bochkanov Sergey.

     Hessian calculation based on R-algorithm described in
     "Fast Exact Multiplication by the Hessian",
     B. A. Pearlmutter,
     Neural Computation, 1994.
*************************************************************************/
void mlphessiannbatch(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t ssize, double &e, real_1d_array &grad, real_2d_array &h)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlphessiannbatch(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), ssize, &e, const_cast<alglib_impl::ae_vector*>(grad.c_ptr()), const_cast<alglib_impl::ae_matrix*>(h.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Batch Hessian calculation using R-algorithm.
Internal subroutine.

  -- ALGLIB --
     Copyright 26.01.2008 by Bochkanov Sergey.

     Hessian calculation based on R-algorithm described in
     "Fast Exact Multiplication by the Hessian",
     B. A. Pearlmutter,
     Neural Computation, 1994.
*************************************************************************/
void mlphessianbatch(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t ssize, double &e, real_1d_array &grad, real_2d_array &h)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlphessianbatch(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), ssize, &e, const_cast<alglib_impl::ae_vector*>(grad.c_ptr()), const_cast<alglib_impl::ae_matrix*>(h.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Calculation of all types of errors.

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.

OUTPUT PARAMETERS:
    Rep     -   it contains all type of errors.

NOTE: when SubsetSize<0 is used full dataset by call MLPGradBatch function.

  -- ALGLIB --
     Copyright 04.09.2012 by Bochkanov Sergey
*************************************************************************/
void mlpallerrorssubset(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t setsize, const integer_1d_array &subset, const ae_int_t subsetsize, modelerrors &rep)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpallerrorssubset(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), setsize, const_cast<alglib_impl::ae_vector*>(subset.c_ptr()), subsetsize, const_cast<alglib_impl::modelerrors*>(rep.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Calculation of all types of errors.

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.

OUTPUT PARAMETERS:
    Rep     -   it contains all type of errors.

NOTE: when SubsetSize<0 is used full dataset by call MLPGradBatch function.

  -- ALGLIB --
     Copyright 04.09.2012 by Bochkanov Sergey
*************************************************************************/
void mlpallerrorssparsesubset(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t setsize, const integer_1d_array &subset, const ae_int_t subsetsize, modelerrors &rep)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpallerrorssparsesubset(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::sparsematrix*>(xy.c_ptr()), setsize, const_cast<alglib_impl::ae_vector*>(subset.c_ptr()), subsetsize, const_cast<alglib_impl::modelerrors*>(rep.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Error of the neural network on 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.

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).

  -- ALGLIB --
     Copyright 04.09.2012 by Bochkanov Sergey
*************************************************************************/
double mlperrorsubset(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t setsize, const integer_1d_array &subset, const ae_int_t subsetsize)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::mlperrorsubset(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), setsize, const_cast<alglib_impl::ae_vector*>(subset.c_ptr()), subsetsize, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Error of the neural network on 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.

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).

  -- ALGLIB --
     Copyright 04.09.2012 by Bochkanov Sergey
*************************************************************************/
double mlperrorsparsesubset(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t setsize, const integer_1d_array &subset, const ae_int_t subsetsize)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::mlperrorsparsesubset(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::sparsematrix*>(xy.c_ptr()), setsize, const_cast<alglib_impl::ae_vector*>(subset.c_ptr()), subsetsize, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************

*************************************************************************/
_logitmodel_owner::_logitmodel_owner()
{
    p_struct = (alglib_impl::logitmodel*)alglib_impl::ae_malloc(sizeof(alglib_impl::logitmodel), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_logitmodel_init(p_struct, NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_logitmodel_owner::_logitmodel_owner(const _logitmodel_owner &rhs)
{
    p_struct = (alglib_impl::logitmodel*)alglib_impl::ae_malloc(sizeof(alglib_impl::logitmodel), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_logitmodel_init_copy(p_struct, const_cast<alglib_impl::logitmodel*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_logitmodel_owner& _logitmodel_owner::operator=(const _logitmodel_owner &rhs)
{
    if( this==&rhs )
        return *this;
    alglib_impl::_logitmodel_clear(p_struct);
    if( !alglib_impl::_logitmodel_init_copy(p_struct, const_cast<alglib_impl::logitmodel*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
    return *this;
}

_logitmodel_owner::~_logitmodel_owner()
{
    alglib_impl::_logitmodel_clear(p_struct);
    ae_free(p_struct);
}

alglib_impl::logitmodel* _logitmodel_owner::c_ptr()
{
    return p_struct;
}

alglib_impl::logitmodel* _logitmodel_owner::c_ptr() const
{
    return const_cast<alglib_impl::logitmodel*>(p_struct);
}
logitmodel::logitmodel() : _logitmodel_owner() 
{
}

logitmodel::logitmodel(const logitmodel &rhs):_logitmodel_owner(rhs) 
{
}

logitmodel& logitmodel::operator=(const logitmodel &rhs)
{
    if( this==&rhs )
        return *this;
    _logitmodel_owner::operator=(rhs);
    return *this;
}

logitmodel::~logitmodel()
{
}


/*************************************************************************
MNLReport structure contains information about training process:
* NGrad     -   number of gradient calculations
* NHess     -   number of Hessian calculations
*************************************************************************/
_mnlreport_owner::_mnlreport_owner()
{
    p_struct = (alglib_impl::mnlreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::mnlreport), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_mnlreport_init(p_struct, NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_mnlreport_owner::_mnlreport_owner(const _mnlreport_owner &rhs)
{
    p_struct = (alglib_impl::mnlreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::mnlreport), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_mnlreport_init_copy(p_struct, const_cast<alglib_impl::mnlreport*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_mnlreport_owner& _mnlreport_owner::operator=(const _mnlreport_owner &rhs)
{
    if( this==&rhs )
        return *this;
    alglib_impl::_mnlreport_clear(p_struct);
    if( !alglib_impl::_mnlreport_init_copy(p_struct, const_cast<alglib_impl::mnlreport*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
    return *this;
}

_mnlreport_owner::~_mnlreport_owner()
{
    alglib_impl::_mnlreport_clear(p_struct);
    ae_free(p_struct);
}

alglib_impl::mnlreport* _mnlreport_owner::c_ptr()
{
    return p_struct;
}

alglib_impl::mnlreport* _mnlreport_owner::c_ptr() const
{
    return const_cast<alglib_impl::mnlreport*>(p_struct);
}
mnlreport::mnlreport() : _mnlreport_owner() ,ngrad(p_struct->ngrad),nhess(p_struct->nhess)
{
}

mnlreport::mnlreport(const mnlreport &rhs):_mnlreport_owner(rhs) ,ngrad(p_struct->ngrad),nhess(p_struct->nhess)
{
}

mnlreport& mnlreport::operator=(const mnlreport &rhs)
{
    if( this==&rhs )
        return *this;
    _mnlreport_owner::operator=(rhs);
    return *this;
}

mnlreport::~mnlreport()
{
}

/*************************************************************************
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

  -- ALGLIB --
     Copyright 10.09.2008 by Bochkanov Sergey
*************************************************************************/
void mnltrainh(const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nvars, const ae_int_t nclasses, ae_int_t &info, logitmodel &lm, mnlreport &rep)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mnltrainh(const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, nvars, nclasses, &info, const_cast<alglib_impl::logitmodel*>(lm.c_ptr()), const_cast<alglib_impl::mnlreport*>(rep.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 10.09.2008 by Bochkanov Sergey
*************************************************************************/
void mnlprocess(const logitmodel &lm, const real_1d_array &x, real_1d_array &y)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mnlprocess(const_cast<alglib_impl::logitmodel*>(lm.c_ptr()), const_cast<alglib_impl::ae_vector*>(x.c_ptr()), const_cast<alglib_impl::ae_vector*>(y.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
'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.

  -- ALGLIB --
     Copyright 10.09.2008 by Bochkanov Sergey
*************************************************************************/
void mnlprocessi(const logitmodel &lm, const real_1d_array &x, real_1d_array &y)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mnlprocessi(const_cast<alglib_impl::logitmodel*>(lm.c_ptr()), const_cast<alglib_impl::ae_vector*>(x.c_ptr()), const_cast<alglib_impl::ae_vector*>(y.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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

  -- ALGLIB --
     Copyright 10.09.2008 by Bochkanov Sergey
*************************************************************************/
void mnlunpack(const logitmodel &lm, real_2d_array &a, ae_int_t &nvars, ae_int_t &nclasses)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mnlunpack(const_cast<alglib_impl::logitmodel*>(lm.c_ptr()), const_cast<alglib_impl::ae_matrix*>(a.c_ptr()), &nvars, &nclasses, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
"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.

  -- ALGLIB --
     Copyright 10.09.2008 by Bochkanov Sergey
*************************************************************************/
void mnlpack(const real_2d_array &a, const ae_int_t nvars, const ae_int_t nclasses, logitmodel &lm)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mnlpack(const_cast<alglib_impl::ae_matrix*>(a.c_ptr()), nvars, nclasses, const_cast<alglib_impl::logitmodel*>(lm.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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)).

  -- ALGLIB --
     Copyright 10.09.2008 by Bochkanov Sergey
*************************************************************************/
double mnlavgce(const logitmodel &lm, const real_2d_array &xy, const ae_int_t npoints)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::mnlavgce(const_cast<alglib_impl::logitmodel*>(lm.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 10.09.2008 by Bochkanov Sergey
*************************************************************************/
double mnlrelclserror(const logitmodel &lm, const real_2d_array &xy, const ae_int_t npoints)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::mnlrelclserror(const_cast<alglib_impl::logitmodel*>(lm.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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).

  -- ALGLIB --
     Copyright 30.08.2008 by Bochkanov Sergey
*************************************************************************/
double mnlrmserror(const logitmodel &lm, const real_2d_array &xy, const ae_int_t npoints)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::mnlrmserror(const_cast<alglib_impl::logitmodel*>(lm.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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).

  -- ALGLIB --
     Copyright 30.08.2008 by Bochkanov Sergey
*************************************************************************/
double mnlavgerror(const logitmodel &lm, const real_2d_array &xy, const ae_int_t npoints)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::mnlavgerror(const_cast<alglib_impl::logitmodel*>(lm.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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).

  -- ALGLIB --
     Copyright 30.08.2008 by Bochkanov Sergey
*************************************************************************/
double mnlavgrelerror(const logitmodel &lm, const real_2d_array &xy, const ae_int_t ssize)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::mnlavgrelerror(const_cast<alglib_impl::logitmodel*>(lm.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), ssize, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Classification error on test set = MNLRelClsError*NPoints

  -- ALGLIB --
     Copyright 10.09.2008 by Bochkanov Sergey
*************************************************************************/
ae_int_t mnlclserror(const logitmodel &lm, const real_2d_array &xy, const ae_int_t npoints)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::ae_int_t result = alglib_impl::mnlclserror(const_cast<alglib_impl::logitmodel*>(lm.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<ae_int_t*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
This structure is a MCPD (Markov Chains for Population Data) solver.

You should use ALGLIB functions in order to work with this object.

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
_mcpdstate_owner::_mcpdstate_owner()
{
    p_struct = (alglib_impl::mcpdstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::mcpdstate), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_mcpdstate_init(p_struct, NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_mcpdstate_owner::_mcpdstate_owner(const _mcpdstate_owner &rhs)
{
    p_struct = (alglib_impl::mcpdstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::mcpdstate), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_mcpdstate_init_copy(p_struct, const_cast<alglib_impl::mcpdstate*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_mcpdstate_owner& _mcpdstate_owner::operator=(const _mcpdstate_owner &rhs)
{
    if( this==&rhs )
        return *this;
    alglib_impl::_mcpdstate_clear(p_struct);
    if( !alglib_impl::_mcpdstate_init_copy(p_struct, const_cast<alglib_impl::mcpdstate*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
    return *this;
}

_mcpdstate_owner::~_mcpdstate_owner()
{
    alglib_impl::_mcpdstate_clear(p_struct);
    ae_free(p_struct);
}

alglib_impl::mcpdstate* _mcpdstate_owner::c_ptr()
{
    return p_struct;
}

alglib_impl::mcpdstate* _mcpdstate_owner::c_ptr() const
{
    return const_cast<alglib_impl::mcpdstate*>(p_struct);
}
mcpdstate::mcpdstate() : _mcpdstate_owner() 
{
}

mcpdstate::mcpdstate(const mcpdstate &rhs):_mcpdstate_owner(rhs) 
{
}

mcpdstate& mcpdstate::operator=(const mcpdstate &rhs)
{
    if( this==&rhs )
        return *this;
    _mcpdstate_owner::operator=(rhs);
    return *this;
}

mcpdstate::~mcpdstate()
{
}


/*************************************************************************
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)

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
_mcpdreport_owner::_mcpdreport_owner()
{
    p_struct = (alglib_impl::mcpdreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::mcpdreport), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_mcpdreport_init(p_struct, NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_mcpdreport_owner::_mcpdreport_owner(const _mcpdreport_owner &rhs)
{
    p_struct = (alglib_impl::mcpdreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::mcpdreport), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_mcpdreport_init_copy(p_struct, const_cast<alglib_impl::mcpdreport*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_mcpdreport_owner& _mcpdreport_owner::operator=(const _mcpdreport_owner &rhs)
{
    if( this==&rhs )
        return *this;
    alglib_impl::_mcpdreport_clear(p_struct);
    if( !alglib_impl::_mcpdreport_init_copy(p_struct, const_cast<alglib_impl::mcpdreport*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
    return *this;
}

_mcpdreport_owner::~_mcpdreport_owner()
{
    alglib_impl::_mcpdreport_clear(p_struct);
    ae_free(p_struct);
}

alglib_impl::mcpdreport* _mcpdreport_owner::c_ptr()
{
    return p_struct;
}

alglib_impl::mcpdreport* _mcpdreport_owner::c_ptr() const
{
    return const_cast<alglib_impl::mcpdreport*>(p_struct);
}
mcpdreport::mcpdreport() : _mcpdreport_owner() ,inneriterationscount(p_struct->inneriterationscount),outeriterationscount(p_struct->outeriterationscount),nfev(p_struct->nfev),terminationtype(p_struct->terminationtype)
{
}

mcpdreport::mcpdreport(const mcpdreport &rhs):_mcpdreport_owner(rhs) ,inneriterationscount(p_struct->inneriterationscount),outeriterationscount(p_struct->outeriterationscount),nfev(p_struct->nfev),terminationtype(p_struct->terminationtype)
{
}

mcpdreport& mcpdreport::operator=(const mcpdreport &rhs)
{
    if( this==&rhs )
        return *this;
    _mcpdreport_owner::operator=(rhs);
    return *this;
}

mcpdreport::~mcpdreport()
{
}

/*************************************************************************
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  P
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

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
void mcpdcreate(const ae_int_t n, mcpdstate &s)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mcpdcreate(n, const_cast<alglib_impl::mcpdstate*>(s.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
void mcpdcreateentry(const ae_int_t n, const ae_int_t entrystate, mcpdstate &s)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mcpdcreateentry(n, entrystate, const_cast<alglib_impl::mcpdstate*>(s.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
void mcpdcreateexit(const ae_int_t n, const ae_int_t exitstate, mcpdstate &s)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mcpdcreateexit(n, exitstate, const_cast<alglib_impl::mcpdstate*>(s.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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 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
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

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
void mcpdcreateentryexit(const ae_int_t n, const ae_int_t entrystate, const ae_int_t exitstate, mcpdstate &s)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mcpdcreateentryexit(n, entrystate, exitstate, const_cast<alglib_impl::mcpdstate*>(s.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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)

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
void mcpdaddtrack(const mcpdstate &s, const real_2d_array &xy, const ae_int_t k)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mcpdaddtrack(const_cast<alglib_impl::mcpdstate*>(s.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), k, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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)

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
void mcpdaddtrack(const mcpdstate &s, const real_2d_array &xy)
{
    alglib_impl::ae_state _alglib_env_state;    
    ae_int_t k;

    k = xy.rows();
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mcpdaddtrack(const_cast<alglib_impl::mcpdstate*>(s.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), k, &_alglib_env_state);

        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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().

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
void mcpdsetec(const mcpdstate &s, const real_2d_array &ec)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mcpdsetec(const_cast<alglib_impl::mcpdstate*>(s.c_ptr()), const_cast<alglib_impl::ae_matrix*>(ec.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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().

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
void mcpdaddec(const mcpdstate &s, const ae_int_t i, const ae_int_t j, const double c)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mcpdaddec(const_cast<alglib_impl::mcpdstate*>(s.c_ptr()), i, j, c, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
void mcpdsetbc(const mcpdstate &s, const real_2d_array &bndl, const real_2d_array &bndu)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mcpdsetbc(const_cast<alglib_impl::mcpdstate*>(s.c_ptr()), const_cast<alglib_impl::ae_matrix*>(bndl.c_ptr()), const_cast<alglib_impl::ae_matrix*>(bndu.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
void mcpdaddbc(const mcpdstate &s, const ae_int_t i, const ae_int_t j, const double bndl, const double bndu)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mcpdaddbc(const_cast<alglib_impl::mcpdstate*>(s.c_ptr()), i, j, bndl, bndu, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
void mcpdsetlc(const mcpdstate &s, const real_2d_array &c, const integer_1d_array &ct, const ae_int_t k)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mcpdsetlc(const_cast<alglib_impl::mcpdstate*>(s.c_ptr()), const_cast<alglib_impl::ae_matrix*>(c.c_ptr()), const_cast<alglib_impl::ae_vector*>(ct.c_ptr()), k, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
void mcpdsetlc(const mcpdstate &s, const real_2d_array &c, const integer_1d_array &ct)
{
    alglib_impl::ae_state _alglib_env_state;    
    ae_int_t k;
    if( (c.rows()!=ct.length()))
        throw ap_error("Error while calling 'mcpdsetlc': looks like one of arguments has wrong size");
    k = c.rows();
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mcpdsetlc(const_cast<alglib_impl::mcpdstate*>(s.c_ptr()), const_cast<alglib_impl::ae_matrix*>(c.c_ptr()), const_cast<alglib_impl::ae_vector*>(ct.c_ptr()), k, &_alglib_env_state);

        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
void mcpdsettikhonovregularizer(const mcpdstate &s, const double v)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mcpdsettikhonovregularizer(const_cast<alglib_impl::mcpdstate*>(s.c_ptr()), v, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
void mcpdsetprior(const mcpdstate &s, const real_2d_array &pp)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mcpdsetprior(const_cast<alglib_impl::mcpdstate*>(s.c_ptr()), const_cast<alglib_impl::ae_matrix*>(pp.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
void mcpdsetpredictionweights(const mcpdstate &s, const real_1d_array &pw)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mcpdsetpredictionweights(const_cast<alglib_impl::mcpdstate*>(s.c_ptr()), const_cast<alglib_impl::ae_vector*>(pw.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
void mcpdsolve(const mcpdstate &s)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mcpdsolve(const_cast<alglib_impl::mcpdstate*>(s.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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  be
                found in the comments on MCPDReport datatype.


  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
void mcpdresults(const mcpdstate &s, real_2d_array &p, mcpdreport &rep)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mcpdresults(const_cast<alglib_impl::mcpdstate*>(s.c_ptr()), const_cast<alglib_impl::ae_matrix*>(p.c_ptr()), const_cast<alglib_impl::mcpdreport*>(rep.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Neural networks ensemble
*************************************************************************/
_mlpensemble_owner::_mlpensemble_owner()
{
    p_struct = (alglib_impl::mlpensemble*)alglib_impl::ae_malloc(sizeof(alglib_impl::mlpensemble), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_mlpensemble_init(p_struct, NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_mlpensemble_owner::_mlpensemble_owner(const _mlpensemble_owner &rhs)
{
    p_struct = (alglib_impl::mlpensemble*)alglib_impl::ae_malloc(sizeof(alglib_impl::mlpensemble), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_mlpensemble_init_copy(p_struct, const_cast<alglib_impl::mlpensemble*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_mlpensemble_owner& _mlpensemble_owner::operator=(const _mlpensemble_owner &rhs)
{
    if( this==&rhs )
        return *this;
    alglib_impl::_mlpensemble_clear(p_struct);
    if( !alglib_impl::_mlpensemble_init_copy(p_struct, const_cast<alglib_impl::mlpensemble*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
    return *this;
}

_mlpensemble_owner::~_mlpensemble_owner()
{
    alglib_impl::_mlpensemble_clear(p_struct);
    ae_free(p_struct);
}

alglib_impl::mlpensemble* _mlpensemble_owner::c_ptr()
{
    return p_struct;
}

alglib_impl::mlpensemble* _mlpensemble_owner::c_ptr() const
{
    return const_cast<alglib_impl::mlpensemble*>(p_struct);
}
mlpensemble::mlpensemble() : _mlpensemble_owner() 
{
}

mlpensemble::mlpensemble(const mlpensemble &rhs):_mlpensemble_owner(rhs) 
{
}

mlpensemble& mlpensemble::operator=(const mlpensemble &rhs)
{
    if( this==&rhs )
        return *this;
    _mlpensemble_owner::operator=(rhs);
    return *this;
}

mlpensemble::~mlpensemble()
{
}


/*************************************************************************
This function serializes data structure to string.

Important properties of s_out:
* it contains alphanumeric characters, dots, underscores, minus signs
* these symbols are grouped into words, which are separated by spaces
  and Windows-style (CR+LF) newlines
* although  serializer  uses  spaces and CR+LF as separators, you can 
  replace any separator character by arbitrary combination of spaces,
  tabs, Windows or Unix newlines. It allows flexible reformatting  of
  the  string  in  case you want to include it into text or XML file. 
  But you should not insert separators into the middle of the "words"
  nor you should change case of letters.
* s_out can be freely moved between 32-bit and 64-bit systems, little
  and big endian machines, and so on. You can serialize structure  on
  32-bit machine and unserialize it on 64-bit one (or vice versa), or
  serialize  it  on  SPARC  and  unserialize  on  x86.  You  can also 
  serialize  it  in  C++ version of ALGLIB and unserialize in C# one, 
  and vice versa.
*************************************************************************/
void mlpeserialize(mlpensemble &obj, std::string &s_out)
{
    alglib_impl::ae_state state;
    alglib_impl::ae_serializer serializer;
    alglib_impl::ae_int_t ssize;

    alglib_impl::ae_state_init(&state);
    try
    {
        alglib_impl::ae_serializer_init(&serializer);
        alglib_impl::ae_serializer_alloc_start(&serializer);
        alglib_impl::mlpealloc(&serializer, obj.c_ptr(), &state);
        ssize = alglib_impl::ae_serializer_get_alloc_size(&serializer);
        s_out.clear();
        s_out.reserve((size_t)(ssize+1));
        alglib_impl::ae_serializer_sstart_str(&serializer, &s_out);
        alglib_impl::mlpeserialize(&serializer, obj.c_ptr(), &state);
        alglib_impl::ae_serializer_stop(&serializer);
        if( s_out.length()>(size_t)ssize )
            throw ap_error("ALGLIB: serialization integrity error");
        alglib_impl::ae_serializer_clear(&serializer);
        alglib_impl::ae_state_clear(&state);
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(state.error_msg);
    }
}
/*************************************************************************
This function unserializes data structure from string.
*************************************************************************/
void mlpeunserialize(std::string &s_in, mlpensemble &obj)
{
    alglib_impl::ae_state state;
    alglib_impl::ae_serializer serializer;

    alglib_impl::ae_state_init(&state);
    try
    {
        alglib_impl::ae_serializer_init(&serializer);
        alglib_impl::ae_serializer_ustart_str(&serializer, &s_in);
        alglib_impl::mlpeunserialize(&serializer, obj.c_ptr(), &state);
        alglib_impl::ae_serializer_stop(&serializer);
        alglib_impl::ae_serializer_clear(&serializer);
        alglib_impl::ae_state_clear(&state);
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(state.error_msg);
    }
}

/*************************************************************************
Like MLPCreate0, but for ensembles.

  -- ALGLIB --
     Copyright 18.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpecreate0(const ae_int_t nin, const ae_int_t nout, const ae_int_t ensemblesize, mlpensemble &ensemble)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpecreate0(nin, nout, ensemblesize, const_cast<alglib_impl::mlpensemble*>(ensemble.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Like MLPCreate1, but for ensembles.

  -- ALGLIB --
     Copyright 18.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpecreate1(const ae_int_t nin, const ae_int_t nhid, const ae_int_t nout, const ae_int_t ensemblesize, mlpensemble &ensemble)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpecreate1(nin, nhid, nout, ensemblesize, const_cast<alglib_impl::mlpensemble*>(ensemble.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Like MLPCreate2, but for ensembles.

  -- ALGLIB --
     Copyright 18.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpecreate2(const ae_int_t nin, const ae_int_t nhid1, const ae_int_t nhid2, const ae_int_t nout, const ae_int_t ensemblesize, mlpensemble &ensemble)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpecreate2(nin, nhid1, nhid2, nout, ensemblesize, const_cast<alglib_impl::mlpensemble*>(ensemble.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Like MLPCreateB0, but for ensembles.

  -- ALGLIB --
     Copyright 18.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpecreateb0(const ae_int_t nin, const ae_int_t nout, const double b, const double d, const ae_int_t ensemblesize, mlpensemble &ensemble)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpecreateb0(nin, nout, b, d, ensemblesize, const_cast<alglib_impl::mlpensemble*>(ensemble.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Like MLPCreateB1, but for ensembles.

  -- ALGLIB --
     Copyright 18.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpecreateb1(const ae_int_t nin, const ae_int_t nhid, const ae_int_t nout, const double b, const double d, const ae_int_t ensemblesize, mlpensemble &ensemble)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpecreateb1(nin, nhid, nout, b, d, ensemblesize, const_cast<alglib_impl::mlpensemble*>(ensemble.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Like MLPCreateB2, but for ensembles.

  -- ALGLIB --
     Copyright 18.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpecreateb2(const ae_int_t nin, const ae_int_t nhid1, const ae_int_t nhid2, const ae_int_t nout, const double b, const double d, const ae_int_t ensemblesize, mlpensemble &ensemble)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpecreateb2(nin, nhid1, nhid2, nout, b, d, ensemblesize, const_cast<alglib_impl::mlpensemble*>(ensemble.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Like MLPCreateR0, but for ensembles.

  -- ALGLIB --
     Copyright 18.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpecreater0(const ae_int_t nin, const ae_int_t nout, const double a, const double b, const ae_int_t ensemblesize, mlpensemble &ensemble)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpecreater0(nin, nout, a, b, ensemblesize, const_cast<alglib_impl::mlpensemble*>(ensemble.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Like MLPCreateR1, but for ensembles.

  -- ALGLIB --
     Copyright 18.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpecreater1(const ae_int_t nin, const ae_int_t nhid, const ae_int_t nout, const double a, const double b, const ae_int_t ensemblesize, mlpensemble &ensemble)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpecreater1(nin, nhid, nout, a, b, ensemblesize, const_cast<alglib_impl::mlpensemble*>(ensemble.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Like MLPCreateR2, but for ensembles.

  -- ALGLIB --
     Copyright 18.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpecreater2(const ae_int_t nin, const ae_int_t nhid1, const ae_int_t nhid2, const ae_int_t nout, const double a, const double b, const ae_int_t ensemblesize, mlpensemble &ensemble)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpecreater2(nin, nhid1, nhid2, nout, a, b, ensemblesize, const_cast<alglib_impl::mlpensemble*>(ensemble.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Like MLPCreateC0, but for ensembles.

  -- ALGLIB --
     Copyright 18.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpecreatec0(const ae_int_t nin, const ae_int_t nout, const ae_int_t ensemblesize, mlpensemble &ensemble)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpecreatec0(nin, nout, ensemblesize, const_cast<alglib_impl::mlpensemble*>(ensemble.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Like MLPCreateC1, but for ensembles.

  -- ALGLIB --
     Copyright 18.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpecreatec1(const ae_int_t nin, const ae_int_t nhid, const ae_int_t nout, const ae_int_t ensemblesize, mlpensemble &ensemble)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpecreatec1(nin, nhid, nout, ensemblesize, const_cast<alglib_impl::mlpensemble*>(ensemble.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Like MLPCreateC2, but for ensembles.

  -- ALGLIB --
     Copyright 18.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpecreatec2(const ae_int_t nin, const ae_int_t nhid1, const ae_int_t nhid2, const ae_int_t nout, const ae_int_t ensemblesize, mlpensemble &ensemble)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpecreatec2(nin, nhid1, nhid2, nout, ensemblesize, const_cast<alglib_impl::mlpensemble*>(ensemble.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Creates ensemble from network. Only network geometry is copied.

  -- ALGLIB --
     Copyright 17.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpecreatefromnetwork(const multilayerperceptron &network, const ae_int_t ensemblesize, mlpensemble &ensemble)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpecreatefromnetwork(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), ensemblesize, const_cast<alglib_impl::mlpensemble*>(ensemble.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Randomization of MLP ensemble

  -- ALGLIB --
     Copyright 17.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlperandomize(const mlpensemble &ensemble)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlperandomize(const_cast<alglib_impl::mlpensemble*>(ensemble.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Return ensemble properties (number of inputs and outputs).

  -- ALGLIB --
     Copyright 17.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpeproperties(const mlpensemble &ensemble, ae_int_t &nin, ae_int_t &nout)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpeproperties(const_cast<alglib_impl::mlpensemble*>(ensemble.c_ptr()), &nin, &nout, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
Return normalization type (whether ensemble is SOFTMAX-normalized or not).

  -- ALGLIB --
     Copyright 17.02.2009 by Bochkanov Sergey
*************************************************************************/
bool mlpeissoftmax(const mlpensemble &ensemble)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        ae_bool result = alglib_impl::mlpeissoftmax(const_cast<alglib_impl::mlpensemble*>(ensemble.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<bool*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 17.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpeprocess(const mlpensemble &ensemble, const real_1d_array &x, real_1d_array &y)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpeprocess(const_cast<alglib_impl::mlpensemble*>(ensemble.c_ptr()), const_cast<alglib_impl::ae_vector*>(x.c_ptr()), const_cast<alglib_impl::ae_vector*>(y.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
'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.

  -- ALGLIB --
     Copyright 17.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpeprocessi(const mlpensemble &ensemble, const real_1d_array &x, real_1d_array &y)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpeprocessi(const_cast<alglib_impl::mlpensemble*>(ensemble.c_ptr()), const_cast<alglib_impl::ae_vector*>(x.c_ptr()), const_cast<alglib_impl::ae_vector*>(y.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 17.02.2009 by Bochkanov Sergey
*************************************************************************/
double mlperelclserror(const mlpensemble &ensemble, const real_2d_array &xy, const ae_int_t npoints)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::mlperelclserror(const_cast<alglib_impl::mlpensemble*>(ensemble.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 17.02.2009 by Bochkanov Sergey
*************************************************************************/
double mlpeavgce(const mlpensemble &ensemble, const real_2d_array &xy, const ae_int_t npoints)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::mlpeavgce(const_cast<alglib_impl::mlpensemble*>(ensemble.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 17.02.2009 by Bochkanov Sergey
*************************************************************************/
double mlpermserror(const mlpensemble &ensemble, const real_2d_array &xy, const ae_int_t npoints)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::mlpermserror(const_cast<alglib_impl::mlpensemble*>(ensemble.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 17.02.2009 by Bochkanov Sergey
*************************************************************************/
double mlpeavgerror(const mlpensemble &ensemble, const real_2d_array &xy, const ae_int_t npoints)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::mlpeavgerror(const_cast<alglib_impl::mlpensemble*>(ensemble.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 17.02.2009 by Bochkanov Sergey
*************************************************************************/
double mlpeavgrelerror(const mlpensemble &ensemble, const real_2d_array &xy, const ae_int_t npoints)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        double result = alglib_impl::mlpeavgrelerror(const_cast<alglib_impl::mlpensemble*>(ensemble.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<double*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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
*************************************************************************/
_mlpreport_owner::_mlpreport_owner()
{
    p_struct = (alglib_impl::mlpreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::mlpreport), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_mlpreport_init(p_struct, NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_mlpreport_owner::_mlpreport_owner(const _mlpreport_owner &rhs)
{
    p_struct = (alglib_impl::mlpreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::mlpreport), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_mlpreport_init_copy(p_struct, const_cast<alglib_impl::mlpreport*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_mlpreport_owner& _mlpreport_owner::operator=(const _mlpreport_owner &rhs)
{
    if( this==&rhs )
        return *this;
    alglib_impl::_mlpreport_clear(p_struct);
    if( !alglib_impl::_mlpreport_init_copy(p_struct, const_cast<alglib_impl::mlpreport*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
    return *this;
}

_mlpreport_owner::~_mlpreport_owner()
{
    alglib_impl::_mlpreport_clear(p_struct);
    ae_free(p_struct);
}

alglib_impl::mlpreport* _mlpreport_owner::c_ptr()
{
    return p_struct;
}

alglib_impl::mlpreport* _mlpreport_owner::c_ptr() const
{
    return const_cast<alglib_impl::mlpreport*>(p_struct);
}
mlpreport::mlpreport() : _mlpreport_owner() ,relclserror(p_struct->relclserror),avgce(p_struct->avgce),rmserror(p_struct->rmserror),avgerror(p_struct->avgerror),avgrelerror(p_struct->avgrelerror),ngrad(p_struct->ngrad),nhess(p_struct->nhess),ncholesky(p_struct->ncholesky)
{
}

mlpreport::mlpreport(const mlpreport &rhs):_mlpreport_owner(rhs) ,relclserror(p_struct->relclserror),avgce(p_struct->avgce),rmserror(p_struct->rmserror),avgerror(p_struct->avgerror),avgrelerror(p_struct->avgrelerror),ngrad(p_struct->ngrad),nhess(p_struct->nhess),ncholesky(p_struct->ncholesky)
{
}

mlpreport& mlpreport::operator=(const mlpreport &rhs)
{
    if( this==&rhs )
        return *this;
    _mlpreport_owner::operator=(rhs);
    return *this;
}

mlpreport::~mlpreport()
{
}


/*************************************************************************
Cross-validation estimates of generalization error
*************************************************************************/
_mlpcvreport_owner::_mlpcvreport_owner()
{
    p_struct = (alglib_impl::mlpcvreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::mlpcvreport), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_mlpcvreport_init(p_struct, NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_mlpcvreport_owner::_mlpcvreport_owner(const _mlpcvreport_owner &rhs)
{
    p_struct = (alglib_impl::mlpcvreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::mlpcvreport), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_mlpcvreport_init_copy(p_struct, const_cast<alglib_impl::mlpcvreport*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_mlpcvreport_owner& _mlpcvreport_owner::operator=(const _mlpcvreport_owner &rhs)
{
    if( this==&rhs )
        return *this;
    alglib_impl::_mlpcvreport_clear(p_struct);
    if( !alglib_impl::_mlpcvreport_init_copy(p_struct, const_cast<alglib_impl::mlpcvreport*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
    return *this;
}

_mlpcvreport_owner::~_mlpcvreport_owner()
{
    alglib_impl::_mlpcvreport_clear(p_struct);
    ae_free(p_struct);
}

alglib_impl::mlpcvreport* _mlpcvreport_owner::c_ptr()
{
    return p_struct;
}

alglib_impl::mlpcvreport* _mlpcvreport_owner::c_ptr() const
{
    return const_cast<alglib_impl::mlpcvreport*>(p_struct);
}
mlpcvreport::mlpcvreport() : _mlpcvreport_owner() ,relclserror(p_struct->relclserror),avgce(p_struct->avgce),rmserror(p_struct->rmserror),avgerror(p_struct->avgerror),avgrelerror(p_struct->avgrelerror)
{
}

mlpcvreport::mlpcvreport(const mlpcvreport &rhs):_mlpcvreport_owner(rhs) ,relclserror(p_struct->relclserror),avgce(p_struct->avgce),rmserror(p_struct->rmserror),avgerror(p_struct->avgerror),avgrelerror(p_struct->avgrelerror)
{
}

mlpcvreport& mlpcvreport::operator=(const mlpcvreport &rhs)
{
    if( this==&rhs )
        return *this;
    _mlpcvreport_owner::operator=(rhs);
    return *this;
}

mlpcvreport::~mlpcvreport()
{
}


/*************************************************************************
Trainer object for neural network.

You should not try to access fields of this object directly -  use  ALGLIB
functions to work with this object.
*************************************************************************/
_mlptrainer_owner::_mlptrainer_owner()
{
    p_struct = (alglib_impl::mlptrainer*)alglib_impl::ae_malloc(sizeof(alglib_impl::mlptrainer), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_mlptrainer_init(p_struct, NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_mlptrainer_owner::_mlptrainer_owner(const _mlptrainer_owner &rhs)
{
    p_struct = (alglib_impl::mlptrainer*)alglib_impl::ae_malloc(sizeof(alglib_impl::mlptrainer), NULL);
    if( p_struct==NULL )
        throw ap_error("ALGLIB: malloc error");
    if( !alglib_impl::_mlptrainer_init_copy(p_struct, const_cast<alglib_impl::mlptrainer*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
}

_mlptrainer_owner& _mlptrainer_owner::operator=(const _mlptrainer_owner &rhs)
{
    if( this==&rhs )
        return *this;
    alglib_impl::_mlptrainer_clear(p_struct);
    if( !alglib_impl::_mlptrainer_init_copy(p_struct, const_cast<alglib_impl::mlptrainer*>(rhs.p_struct), NULL, ae_false) )
        throw ap_error("ALGLIB: malloc error");
    return *this;
}

_mlptrainer_owner::~_mlptrainer_owner()
{
    alglib_impl::_mlptrainer_clear(p_struct);
    ae_free(p_struct);
}

alglib_impl::mlptrainer* _mlptrainer_owner::c_ptr()
{
    return p_struct;
}

alglib_impl::mlptrainer* _mlptrainer_owner::c_ptr() const
{
    return const_cast<alglib_impl::mlptrainer*>(p_struct);
}
mlptrainer::mlptrainer() : _mlptrainer_owner() 
{
}

mlptrainer::mlptrainer(const mlptrainer &rhs):_mlptrainer_owner(rhs) 
{
}

mlptrainer& mlptrainer::operator=(const mlptrainer &rhs)
{
    if( this==&rhs )
        return *this;
    _mlptrainer_owner::operator=(rhs);
    return *this;
}

mlptrainer::~mlptrainer()
{
}

/*************************************************************************
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

  -- ALGLIB --
     Copyright 10.03.2009 by Bochkanov Sergey
*************************************************************************/
void mlptrainlm(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints, const double decay, const ae_int_t restarts, ae_int_t &info, mlpreport &rep)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlptrainlm(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, decay, restarts, &info, const_cast<alglib_impl::mlpreport*>(rep.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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

  -- ALGLIB --
     Copyright 09.12.2007 by Bochkanov Sergey
*************************************************************************/
void mlptrainlbfgs(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints, const double decay, const ae_int_t restarts, const double wstep, const ae_int_t maxits, ae_int_t &info, mlpreport &rep)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlptrainlbfgs(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, decay, restarts, wstep, maxits, &info, const_cast<alglib_impl::mlpreport*>(rep.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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, TrnSize>0
    ValXY       -   validation set
    ValSize     -   validation set size, ValSize>0
    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, either:
                    * strictly positive number - algorithm make specified
                      number of restarts from random position.
                    * -1, in which case algorithm makes exactly one run
                      from the initial state of the network (no randomization).
                    If you don't know what Restarts to choose, choose one
                    one the following:
                    * -1 (deterministic start)
                    * +1 (one random restart)
                    * +5 (moderate amount of random restarts)

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.

  -- ALGLIB --
     Copyright 10.03.2009 by Bochkanov Sergey
*************************************************************************/
void mlptraines(const multilayerperceptron &network, const real_2d_array &trnxy, const ae_int_t trnsize, const real_2d_array &valxy, const ae_int_t valsize, const double decay, const ae_int_t restarts, ae_int_t &info, mlpreport &rep)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlptraines(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::ae_matrix*>(trnxy.c_ptr()), trnsize, const_cast<alglib_impl::ae_matrix*>(valxy.c_ptr()), valsize, decay, restarts, &info, const_cast<alglib_impl::mlpreport*>(rep.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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

  -- ALGLIB --
     Copyright 09.12.2007 by Bochkanov Sergey
*************************************************************************/
void mlpkfoldcvlbfgs(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints, const double decay, const ae_int_t restarts, const double wstep, const ae_int_t maxits, const ae_int_t foldscount, ae_int_t &info, mlpreport &rep, mlpcvreport &cvrep)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpkfoldcvlbfgs(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, decay, restarts, wstep, maxits, foldscount, &info, const_cast<alglib_impl::mlpreport*>(rep.c_ptr()), const_cast<alglib_impl::mlpcvreport*>(cvrep.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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

  -- ALGLIB --
     Copyright 09.12.2007 by Bochkanov Sergey
*************************************************************************/
void mlpkfoldcvlm(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints, const double decay, const ae_int_t restarts, const ae_int_t foldscount, ae_int_t &info, mlpreport &rep, mlpcvreport &cvrep)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpkfoldcvlm(const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, decay, restarts, foldscount, &info, const_cast<alglib_impl::mlpreport*>(rep.c_ptr()), const_cast<alglib_impl::mlpcvreport*>(cvrep.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
This function estimates generalization error using cross-validation on the
current dataset with current training settings.


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. Network is not changed  during  cross-
                    validation and is not trained - it  is  used  only  as
                    representative of its architecture. I.e., we  estimate
                    generalization properties of  ARCHITECTURE,  not  some
                    specific network.
    NRestarts   -   number of restarts, >=0:
                    * NRestarts>0  means  that  for  each cross-validation
                      round   specified  number   of  random  restarts  is
                      performed,  with  best  network  being  chosen after
                      training.
                    * NRestarts=0 is same as NRestarts=1
    FoldsCount  -   number of folds in k-fold cross-validation:
                    * 2<=FoldsCount<=size of dataset
                    * recommended value: 10.
                    * values larger than dataset size will be silently
                      truncated down to dataset size

OUTPUT PARAMETERS:
    Rep         -   structure which contains cross-validation estimates:
                    * Rep.RelCLSError - fraction of misclassified cases.
                    * Rep.AvgCE - acerage cross-entropy
                    * Rep.RMSError - root-mean-square error
                    * Rep.AvgError - average error
                    * Rep.AvgRelError - average relative error

NOTE: when no dataset was specified with MLPSetDataset/SetSparseDataset(),
      or subset with only one point  was  given,  zeros  are  returned  as
      estimates.

NOTE: this method performs FoldsCount cross-validation  rounds,  each  one
      with NRestarts random starts.  Thus,  FoldsCount*NRestarts  networks
      are trained in total.

NOTE: Rep.RelCLSError/Rep.AvgCE are zero on regression problems.

NOTE: on classification problems Rep.RMSError/Rep.AvgError/Rep.AvgRelError
      contain errors in prediction of posterior probabilities.

  -- ALGLIB --
     Copyright 23.07.2012 by Bochkanov Sergey
*************************************************************************/
void mlpkfoldcv(const mlptrainer &s, const multilayerperceptron &network, const ae_int_t nrestarts, const ae_int_t foldscount, mlpreport &rep)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpkfoldcv(const_cast<alglib_impl::mlptrainer*>(s.c_ptr()), const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), nrestarts, foldscount, const_cast<alglib_impl::mlpreport*>(rep.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}


void smp_mlpkfoldcv(const mlptrainer &s, const multilayerperceptron &network, const ae_int_t nrestarts, const ae_int_t foldscount, mlpreport &rep)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::_pexec_mlpkfoldcv(const_cast<alglib_impl::mlptrainer*>(s.c_ptr()), const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), nrestarts, foldscount, const_cast<alglib_impl::mlpreport*>(rep.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 23.07.2012 by Bochkanov Sergey
*************************************************************************/
void mlpcreatetrainer(const ae_int_t nin, const ae_int_t nout, mlptrainer &s)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpcreatetrainer(nin, nout, const_cast<alglib_impl::mlptrainer*>(s.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 23.07.2012 by Bochkanov Sergey
*************************************************************************/
void mlpcreatetrainercls(const ae_int_t nin, const ae_int_t nclasses, mlptrainer &s)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpcreatetrainercls(nin, nclasses, const_cast<alglib_impl::mlptrainer*>(s.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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).

  -- ALGLIB --
     Copyright 23.07.2012 by Bochkanov Sergey
*************************************************************************/
void mlpsetdataset(const mlptrainer &s, const real_2d_array &xy, const ae_int_t npoints)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpsetdataset(const_cast<alglib_impl::mlptrainer*>(s.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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).

  -- ALGLIB --
     Copyright 23.07.2012 by Bochkanov Sergey
*************************************************************************/
void mlpsetsparsedataset(const mlptrainer &s, const sparsematrix &xy, const ae_int_t npoints)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpsetsparsedataset(const_cast<alglib_impl::mlptrainer*>(s.c_ptr()), const_cast<alglib_impl::sparsematrix*>(xy.c_ptr()), npoints, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 23.07.2012 by Bochkanov Sergey
*************************************************************************/
void mlpsetdecay(const mlptrainer &s, const double decay)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpsetdecay(const_cast<alglib_impl::mlptrainer*>(s.c_ptr()), decay, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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
                    iterations (NOT gradient  calculations).  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.

  -- ALGLIB --
     Copyright 23.07.2012 by Bochkanov Sergey
*************************************************************************/
void mlpsetcond(const mlptrainer &s, const double wstep, const ae_int_t maxits)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpsetcond(const_cast<alglib_impl::mlptrainer*>(s.c_ptr()), wstep, maxits, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 23.07.2012 by Bochkanov Sergey
*************************************************************************/
void mlptrainnetwork(const mlptrainer &s, const multilayerperceptron &network, const ae_int_t nrestarts, mlpreport &rep)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlptrainnetwork(const_cast<alglib_impl::mlptrainer*>(s.c_ptr()), const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), nrestarts, const_cast<alglib_impl::mlpreport*>(rep.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.

  -- ALGLIB --
     Copyright 23.07.2012 by Bochkanov Sergey
*************************************************************************/
void mlpstarttraining(const mlptrainer &s, const multilayerperceptron &network, const bool randomstart)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpstarttraining(const_cast<alglib_impl::mlptrainer*>(s.c_ptr()), const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), randomstart, &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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.


  -- ALGLIB --
     Copyright 23.07.2012 by Bochkanov Sergey
*************************************************************************/
bool mlpcontinuetraining(const mlptrainer &s, const multilayerperceptron &network)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        ae_bool result = alglib_impl::mlpcontinuetraining(const_cast<alglib_impl::mlptrainer*>(s.c_ptr()), const_cast<alglib_impl::multilayerperceptron*>(network.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return *(reinterpret_cast<bool*>(&result));
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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

  -- ALGLIB --
     Copyright 17.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpebagginglm(const mlpensemble &ensemble, const real_2d_array &xy, const ae_int_t npoints, const double decay, const ae_int_t restarts, ae_int_t &info, mlpreport &rep, mlpcvreport &ooberrors)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpebagginglm(const_cast<alglib_impl::mlpensemble*>(ensemble.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, decay, restarts, &info, const_cast<alglib_impl::mlpreport*>(rep.c_ptr()), const_cast<alglib_impl::mlpcvreport*>(ooberrors.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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

  -- ALGLIB --
     Copyright 17.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpebagginglbfgs(const mlpensemble &ensemble, const real_2d_array &xy, const ae_int_t npoints, const double decay, const ae_int_t restarts, const double wstep, const ae_int_t maxits, ae_int_t &info, mlpreport &rep, mlpcvreport &ooberrors)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpebagginglbfgs(const_cast<alglib_impl::mlpensemble*>(ensemble.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, decay, restarts, wstep, maxits, &info, const_cast<alglib_impl::mlpreport*>(rep.c_ptr()), const_cast<alglib_impl::mlpcvreport*>(ooberrors.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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

  -- ALGLIB --
     Copyright 10.03.2009 by Bochkanov Sergey
*************************************************************************/
void mlpetraines(const mlpensemble &ensemble, const real_2d_array &xy, const ae_int_t npoints, const double decay, const ae_int_t restarts, ae_int_t &info, mlpreport &rep)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlpetraines(const_cast<alglib_impl::mlpensemble*>(ensemble.c_ptr()), const_cast<alglib_impl::ae_matrix*>(xy.c_ptr()), npoints, decay, restarts, &info, const_cast<alglib_impl::mlpreport*>(rep.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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: 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.

  -- ALGLIB --
     Copyright 22.08.2012 by Bochkanov Sergey
*************************************************************************/
void mlptrainensemblees(const mlptrainer &s, const mlpensemble &ensemble, const ae_int_t nrestarts, mlpreport &rep)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::mlptrainensemblees(const_cast<alglib_impl::mlptrainer*>(s.c_ptr()), const_cast<alglib_impl::mlpensemble*>(ensemble.c_ptr()), nrestarts, const_cast<alglib_impl::mlpreport*>(rep.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}

/*************************************************************************
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

ÂÛÕÎÄÍÛÅ ÏÀÐÀÌÅÒÐÛ:
    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.

  -- ALGLIB --
     Copyright 25.08.2008 by Bochkanov Sergey
*************************************************************************/
void pcabuildbasis(const real_2d_array &x, const ae_int_t npoints, const ae_int_t nvars, ae_int_t &info, real_1d_array &s2, real_2d_array &v)
{
    alglib_impl::ae_state _alglib_env_state;
    alglib_impl::ae_state_init(&_alglib_env_state);
    try
    {
        alglib_impl::pcabuildbasis(const_cast<alglib_impl::ae_matrix*>(x.c_ptr()), npoints, nvars, &info, const_cast<alglib_impl::ae_vector*>(s2.c_ptr()), const_cast<alglib_impl::ae_matrix*>(v.c_ptr()), &_alglib_env_state);
        alglib_impl::ae_state_clear(&_alglib_env_state);
        return;
    }
    catch(alglib_impl::ae_error_type)
    {
        throw ap_error(_alglib_env_state.error_msg);
    }
}
}

/////////////////////////////////////////////////////////////////////////
//
// THIS SECTION CONTAINS IMPLEMENTATION OF COMPUTATIONAL CORE
//
/////////////////////////////////////////////////////////////////////////
namespace alglib_impl
{
static double bdss_xlny(double x, double y, ae_state *_state);
static double bdss_getcv(/* Integer */ ae_vector* cnt,
     ae_int_t nc,
     ae_state *_state);
static void bdss_tieaddc(/* Integer */ ae_vector* c,
     /* Integer */ ae_vector* ties,
     ae_int_t ntie,
     ae_int_t nc,
     /* Integer */ ae_vector* cnt,
     ae_state *_state);
static void bdss_tiesubc(/* Integer */ ae_vector* c,
     /* Integer */ ae_vector* ties,
     ae_int_t ntie,
     ae_int_t nc,
     /* Integer */ ae_vector* cnt,
     ae_state *_state);


static ae_bool clustering_selectcenterpp(/* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_int_t nvars,
     /* Real    */ ae_matrix* centers,
     /* Boolean */ ae_vector* busycenters,
     ae_int_t ccnt,
     /* Real    */ ae_vector* d2,
     /* Real    */ ae_vector* p,
     /* Real    */ ae_vector* tmp,
     ae_state *_state);
static void clustering_clusterizerrunahcinternal(clusterizerstate* s,
     /* Real    */ ae_matrix* d,
     ahcreport* rep,
     ae_state *_state);




static ae_int_t dforest_innernodewidth = 3;
static ae_int_t dforest_leafnodewidth = 2;
static ae_int_t dforest_dfusestrongsplits = 1;
static ae_int_t dforest_dfuseevs = 2;
static ae_int_t dforest_dffirstversion = 0;
static ae_int_t dforest_dfclserror(decisionforest* df,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_state *_state);
static void dforest_dfprocessinternal(decisionforest* df,
     ae_int_t offs,
     /* Real    */ ae_vector* x,
     /* Real    */ ae_vector* y,
     ae_state *_state);
static void dforest_dfbuildtree(/* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_int_t nvars,
     ae_int_t nclasses,
     ae_int_t nfeatures,
     ae_int_t nvarsinpool,
     ae_int_t flags,
     dfinternalbuffers* bufs,
     ae_state *_state);
static void dforest_dfbuildtreerec(/* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_int_t nvars,
     ae_int_t nclasses,
     ae_int_t nfeatures,
     ae_int_t nvarsinpool,
     ae_int_t flags,
     ae_int_t* numprocessed,
     ae_int_t idx1,
     ae_int_t idx2,
     dfinternalbuffers* bufs,
     ae_state *_state);
static void dforest_dfsplitc(/* Real    */ ae_vector* x,
     /* Integer */ ae_vector* c,
     /* Integer */ ae_vector* cntbuf,
     ae_int_t n,
     ae_int_t nc,
     ae_int_t flags,
     ae_int_t* info,
     double* threshold,
     double* e,
     /* Real    */ ae_vector* sortrbuf,
     /* Integer */ ae_vector* sortibuf,
     ae_state *_state);
static void dforest_dfsplitr(/* Real    */ ae_vector* x,
     /* Real    */ ae_vector* y,
     ae_int_t n,
     ae_int_t flags,
     ae_int_t* info,
     double* threshold,
     double* e,
     /* Real    */ ae_vector* sortrbuf,
     /* Real    */ ae_vector* sortrbuf2,
     ae_state *_state);


static ae_int_t linreg_lrvnum = 5;
static void linreg_lrinternal(/* Real    */ ae_matrix* xy,
     /* Real    */ ae_vector* s,
     ae_int_t npoints,
     ae_int_t nvars,
     ae_int_t* info,
     linearmodel* lm,
     lrreport* ar,
     ae_state *_state);






static ae_int_t mlpbase_mlpvnum = 7;
static ae_int_t mlpbase_mlpfirstversion = 0;
static ae_int_t mlpbase_nfieldwidth = 4;
static ae_int_t mlpbase_hlconnfieldwidth = 5;
static ae_int_t mlpbase_hlnfieldwidth = 4;
static ae_int_t mlpbase_chunksize = 32;
static void mlpbase_addinputlayer(ae_int_t ncount,
     /* Integer */ ae_vector* lsizes,
     /* Integer */ ae_vector* ltypes,
     /* Integer */ ae_vector* lconnfirst,
     /* Integer */ ae_vector* lconnlast,
     ae_int_t* lastproc,
     ae_state *_state);
static void mlpbase_addbiasedsummatorlayer(ae_int_t ncount,
     /* Integer */ ae_vector* lsizes,
     /* Integer */ ae_vector* ltypes,
     /* Integer */ ae_vector* lconnfirst,
     /* Integer */ ae_vector* lconnlast,
     ae_int_t* lastproc,
     ae_state *_state);
static void mlpbase_addactivationlayer(ae_int_t functype,
     /* Integer */ ae_vector* lsizes,
     /* Integer */ ae_vector* ltypes,
     /* Integer */ ae_vector* lconnfirst,
     /* Integer */ ae_vector* lconnlast,
     ae_int_t* lastproc,
     ae_state *_state);
static void mlpbase_addzerolayer(/* Integer */ ae_vector* lsizes,
     /* Integer */ ae_vector* ltypes,
     /* Integer */ ae_vector* lconnfirst,
     /* Integer */ ae_vector* lconnlast,
     ae_int_t* lastproc,
     ae_state *_state);
static void mlpbase_hladdinputlayer(multilayerperceptron* network,
     ae_int_t* connidx,
     ae_int_t* neuroidx,
     ae_int_t* structinfoidx,
     ae_int_t nin,
     ae_state *_state);
static void mlpbase_hladdoutputlayer(multilayerperceptron* network,
     ae_int_t* connidx,
     ae_int_t* neuroidx,
     ae_int_t* structinfoidx,
     ae_int_t* weightsidx,
     ae_int_t k,
     ae_int_t nprev,
     ae_int_t nout,
     ae_bool iscls,
     ae_bool islinearout,
     ae_state *_state);
static void mlpbase_hladdhiddenlayer(multilayerperceptron* network,
     ae_int_t* connidx,
     ae_int_t* neuroidx,
     ae_int_t* structinfoidx,
     ae_int_t* weightsidx,
     ae_int_t k,
     ae_int_t nprev,
     ae_int_t ncur,
     ae_state *_state);
static void mlpbase_fillhighlevelinformation(multilayerperceptron* network,
     ae_int_t nin,
     ae_int_t nhid1,
     ae_int_t nhid2,
     ae_int_t nout,
     ae_bool iscls,
     ae_bool islinearout,
     ae_state *_state);
static void mlpbase_mlpcreate(ae_int_t nin,
     ae_int_t nout,
     /* Integer */ ae_vector* lsizes,
     /* Integer */ ae_vector* ltypes,
     /* Integer */ ae_vector* lconnfirst,
     /* Integer */ ae_vector* lconnlast,
     ae_int_t layerscount,
     ae_bool isclsnet,
     multilayerperceptron* network,
     ae_state *_state);
static void mlpbase_mlphessianbatchinternal(multilayerperceptron* network,
     /* Real    */ ae_matrix* xy,
     ae_int_t ssize,
     ae_bool naturalerr,
     double* e,
     /* Real    */ ae_vector* grad,
     /* Real    */ ae_matrix* h,
     ae_state *_state);
static void mlpbase_mlpinternalcalculategradient(multilayerperceptron* network,
     /* Real    */ ae_vector* neurons,
     /* Real    */ ae_vector* weights,
     /* Real    */ ae_vector* derror,
     /* Real    */ ae_vector* grad,
     ae_bool naturalerrorfunc,
     ae_state *_state);
static void mlpbase_mlpchunkedgradient(multilayerperceptron* network,
     /* Real    */ ae_matrix* xy,
     ae_int_t cstart,
     ae_int_t csize,
     double* e,
     /* Real    */ ae_vector* grad,
     ae_bool naturalerrorfunc,
     ae_state *_state);
static double mlpbase_safecrossentropy(double t,
     double z,
     ae_state *_state);


static double logit_xtol = 100*ae_machineepsilon;
static double logit_ftol = 0.0001;
static double logit_gtol = 0.3;
static ae_int_t logit_maxfev = 20;
static double logit_stpmin = 1.0E-2;
static double logit_stpmax = 1.0E5;
static ae_int_t logit_logitvnum = 6;
static void logit_mnliexp(/* Real    */ ae_vector* w,
     /* Real    */ ae_vector* x,
     ae_state *_state);
static void logit_mnlallerrors(logitmodel* lm,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     double* relcls,
     double* avgce,
     double* rms,
     double* avg,
     double* avgrel,
     ae_state *_state);
static void logit_mnlmcsrch(ae_int_t n,
     /* Real    */ ae_vector* x,
     double* f,
     /* Real    */ ae_vector* g,
     /* Real    */ ae_vector* s,
     double* stp,
     ae_int_t* info,
     ae_int_t* nfev,
     /* Real    */ ae_vector* wa,
     logitmcstate* state,
     ae_int_t* stage,
     ae_state *_state);
static void logit_mnlmcstep(double* stx,
     double* fx,
     double* dx,
     double* sty,
     double* fy,
     double* dy,
     double* stp,
     double fp,
     double dp,
     ae_bool* brackt,
     double stmin,
     double stmax,
     ae_int_t* info,
     ae_state *_state);


static double mcpd_xtol = 1.0E-8;
static void mcpd_mcpdinit(ae_int_t n,
     ae_int_t entrystate,
     ae_int_t exitstate,
     mcpdstate* s,
     ae_state *_state);


static ae_int_t mlpe_mlpefirstversion = 1;


static double mlptrain_mindecay = 0.001;
static void mlptrain_mlpkfoldcvgeneral(multilayerperceptron* n,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     double decay,
     ae_int_t restarts,
     ae_int_t foldscount,
     ae_bool lmalgorithm,
     double wstep,
     ae_int_t maxits,
     ae_int_t* info,
     mlpreport* rep,
     mlpcvreport* cvrep,
     ae_state *_state);
static void mlptrain_mlpkfoldsplit(/* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_int_t nclasses,
     ae_int_t foldscount,
     ae_bool stratifiedsplits,
     /* Integer */ ae_vector* folds,
     ae_state *_state);
static void mlptrain_mthreadcv(mlptrainer* s,
     ae_int_t rowsize,
     ae_int_t nrestarts,
     /* Integer */ ae_vector* folds,
     ae_int_t fold,
     ae_int_t dfold,
     /* Real    */ ae_matrix* cvy,
     ae_shared_pool* pooldatacv,
     ae_state *_state);
static void mlptrain_mlptrainnetworkx(mlptrainer* s,
     multilayerperceptron* network,
     multilayerperceptron* tnetwork,
     minlbfgsstate* state,
     ae_int_t nrestarts,
     /* Integer */ ae_vector* trnsubset,
     ae_int_t trnsubsetsize,
     /* Integer */ ae_vector* valsubset,
     ae_int_t valsubsetsize,
     /* Real    */ ae_vector* bufwbest,
     /* Real    */ ae_vector* bufwfinal,
     mlpreport* rep,
     ae_state *_state);
static void mlptrain_mlpstarttrainingx(mlptrainer* s,
     multilayerperceptron* network,
     multilayerperceptron* tnetwork,
     minlbfgsstate* state,
     ae_bool randomstart,
     /* Integer */ ae_vector* subset,
     ae_int_t subsetsize,
     ae_state *_state);
static ae_bool mlptrain_mlpcontinuetrainingx(mlptrainer* s,
     multilayerperceptron* network,
     multilayerperceptron* tnetwork,
     minlbfgsstate* state,
     /* Integer */ ae_vector* subset,
     ae_int_t subsetsize,
     ae_int_t* ngradbatch,
     ae_state *_state);
static void mlptrain_mlpebagginginternal(mlpensemble* ensemble,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     double decay,
     ae_int_t restarts,
     double wstep,
     ae_int_t maxits,
     ae_bool lmalgorithm,
     ae_int_t* info,
     mlpreport* rep,
     mlpcvreport* ooberrors,
     ae_state *_state);







/*************************************************************************
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.

  -- ALGLIB --
     Copyright 11.01.2009 by Bochkanov Sergey
*************************************************************************/
void dserrallocate(ae_int_t nclasses,
     /* Real    */ ae_vector* buf,
     ae_state *_state)
{

    ae_vector_clear(buf);

    ae_vector_set_length(buf, 7+1, _state);
    buf->ptr.p_double[0] = 0;
    buf->ptr.p_double[1] = 0;
    buf->ptr.p_double[2] = 0;
    buf->ptr.p_double[3] = 0;
    buf->ptr.p_double[4] = 0;
    buf->ptr.p_double[5] = nclasses;
    buf->ptr.p_double[6] = 0;
    buf->ptr.p_double[7] = 0;
}


/*************************************************************************
See DSErrAllocate for comments on this routine.

  -- ALGLIB --
     Copyright 11.01.2009 by Bochkanov Sergey
*************************************************************************/
void dserraccumulate(/* Real    */ ae_vector* buf,
     /* Real    */ ae_vector* y,
     /* Real    */ ae_vector* desiredy,
     ae_state *_state)
{
    ae_int_t nclasses;
    ae_int_t nout;
    ae_int_t offs;
    ae_int_t mmax;
    ae_int_t rmax;
    ae_int_t j;
    double v;
    double ev;


    offs = 5;
    nclasses = ae_round(buf->ptr.p_double[offs], _state);
    if( nclasses>0 )
    {
        
        /*
         * Classification
         */
        rmax = ae_round(desiredy->ptr.p_double[0], _state);
        mmax = 0;
        for(j=1; j<=nclasses-1; j++)
        {
            if( ae_fp_greater(y->ptr.p_double[j],y->ptr.p_double[mmax]) )
            {
                mmax = j;
            }
        }
        if( mmax!=rmax )
        {
            buf->ptr.p_double[0] = buf->ptr.p_double[0]+1;
        }
        if( ae_fp_greater(y->ptr.p_double[rmax],0) )
        {
            buf->ptr.p_double[1] = buf->ptr.p_double[1]-ae_log(y->ptr.p_double[rmax], _state);
        }
        else
        {
            buf->ptr.p_double[1] = buf->ptr.p_double[1]+ae_log(ae_maxrealnumber, _state);
        }
        for(j=0; j<=nclasses-1; j++)
        {
            v = y->ptr.p_double[j];
            if( j==rmax )
            {
                ev = 1;
            }
            else
            {
                ev = 0;
            }
            buf->ptr.p_double[2] = buf->ptr.p_double[2]+ae_sqr(v-ev, _state);
            buf->ptr.p_double[3] = buf->ptr.p_double[3]+ae_fabs(v-ev, _state);
            if( ae_fp_neq(ev,0) )
            {
                buf->ptr.p_double[4] = buf->ptr.p_double[4]+ae_fabs((v-ev)/ev, _state);
                buf->ptr.p_double[offs+2] = buf->ptr.p_double[offs+2]+1;
            }
        }
        buf->ptr.p_double[offs+1] = buf->ptr.p_double[offs+1]+1;
    }
    else
    {
        
        /*
         * Regression
         */
        nout = -nclasses;
        rmax = 0;
        for(j=1; j<=nout-1; j++)
        {
            if( ae_fp_greater(desiredy->ptr.p_double[j],desiredy->ptr.p_double[rmax]) )
            {
                rmax = j;
            }
        }
        mmax = 0;
        for(j=1; j<=nout-1; j++)
        {
            if( ae_fp_greater(y->ptr.p_double[j],y->ptr.p_double[mmax]) )
            {
                mmax = j;
            }
        }
        if( mmax!=rmax )
        {
            buf->ptr.p_double[0] = buf->ptr.p_double[0]+1;
        }
        for(j=0; j<=nout-1; j++)
        {
            v = y->ptr.p_double[j];
            ev = desiredy->ptr.p_double[j];
            buf->ptr.p_double[2] = buf->ptr.p_double[2]+ae_sqr(v-ev, _state);
            buf->ptr.p_double[3] = buf->ptr.p_double[3]+ae_fabs(v-ev, _state);
            if( ae_fp_neq(ev,0) )
            {
                buf->ptr.p_double[4] = buf->ptr.p_double[4]+ae_fabs((v-ev)/ev, _state);
                buf->ptr.p_double[offs+2] = buf->ptr.p_double[offs+2]+1;
            }
        }
        buf->ptr.p_double[offs+1] = buf->ptr.p_double[offs+1]+1;
    }
}


/*************************************************************************
See DSErrAllocate for comments on this routine.

  -- ALGLIB --
     Copyright 11.01.2009 by Bochkanov Sergey
*************************************************************************/
void dserrfinish(/* Real    */ ae_vector* buf, ae_state *_state)
{
    ae_int_t nout;
    ae_int_t offs;


    offs = 5;
    nout = ae_iabs(ae_round(buf->ptr.p_double[offs], _state), _state);
    if( ae_fp_neq(buf->ptr.p_double[offs+1],0) )
    {
        buf->ptr.p_double[0] = buf->ptr.p_double[0]/buf->ptr.p_double[offs+1];
        buf->ptr.p_double[1] = buf->ptr.p_double[1]/buf->ptr.p_double[offs+1];
        buf->ptr.p_double[2] = ae_sqrt(buf->ptr.p_double[2]/(nout*buf->ptr.p_double[offs+1]), _state);
        buf->ptr.p_double[3] = buf->ptr.p_double[3]/(nout*buf->ptr.p_double[offs+1]);
    }
    if( ae_fp_neq(buf->ptr.p_double[offs+2],0) )
    {
        buf->ptr.p_double[4] = buf->ptr.p_double[4]/buf->ptr.p_double[offs+2];
    }
}


/*************************************************************************

  -- ALGLIB --
     Copyright 19.05.2008 by Bochkanov Sergey
*************************************************************************/
void dsnormalize(/* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_int_t nvars,
     ae_int_t* info,
     /* Real    */ ae_vector* means,
     /* Real    */ ae_vector* sigmas,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t i;
    ae_int_t j;
    ae_vector tmp;
    double mean;
    double variance;
    double skewness;
    double kurtosis;

    ae_frame_make(_state, &_frame_block);
    *info = 0;
    ae_vector_clear(means);
    ae_vector_clear(sigmas);
    ae_vector_init(&tmp, 0, DT_REAL, _state, ae_true);

    
    /*
     * Test parameters
     */
    if( npoints<=0||nvars<1 )
    {
        *info = -1;
        ae_frame_leave(_state);
        return;
    }
    *info = 1;
    
    /*
     * Standartization
     */
    ae_vector_set_length(means, nvars-1+1, _state);
    ae_vector_set_length(sigmas, nvars-1+1, _state);
    ae_vector_set_length(&tmp, npoints-1+1, _state);
    for(j=0; j<=nvars-1; j++)
    {
        ae_v_move(&tmp.ptr.p_double[0], 1, &xy->ptr.pp_double[0][j], xy->stride, ae_v_len(0,npoints-1));
        samplemoments(&tmp, npoints, &mean, &variance, &skewness, &kurtosis, _state);
        means->ptr.p_double[j] = mean;
        sigmas->ptr.p_double[j] = ae_sqrt(variance, _state);
        if( ae_fp_eq(sigmas->ptr.p_double[j],0) )
        {
            sigmas->ptr.p_double[j] = 1;
        }
        for(i=0; i<=npoints-1; i++)
        {
            xy->ptr.pp_double[i][j] = (xy->ptr.pp_double[i][j]-means->ptr.p_double[j])/sigmas->ptr.p_double[j];
        }
    }
    ae_frame_leave(_state);
}


/*************************************************************************

  -- ALGLIB --
     Copyright 19.05.2008 by Bochkanov Sergey
*************************************************************************/
void dsnormalizec(/* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_int_t nvars,
     ae_int_t* info,
     /* Real    */ ae_vector* means,
     /* Real    */ ae_vector* sigmas,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t j;
    ae_vector tmp;
    double mean;
    double variance;
    double skewness;
    double kurtosis;

    ae_frame_make(_state, &_frame_block);
    *info = 0;
    ae_vector_clear(means);
    ae_vector_clear(sigmas);
    ae_vector_init(&tmp, 0, DT_REAL, _state, ae_true);

    
    /*
     * Test parameters
     */
    if( npoints<=0||nvars<1 )
    {
        *info = -1;
        ae_frame_leave(_state);
        return;
    }
    *info = 1;
    
    /*
     * Standartization
     */
    ae_vector_set_length(means, nvars-1+1, _state);
    ae_vector_set_length(sigmas, nvars-1+1, _state);
    ae_vector_set_length(&tmp, npoints-1+1, _state);
    for(j=0; j<=nvars-1; j++)
    {
        ae_v_move(&tmp.ptr.p_double[0], 1, &xy->ptr.pp_double[0][j], xy->stride, ae_v_len(0,npoints-1));
        samplemoments(&tmp, npoints, &mean, &variance, &skewness, &kurtosis, _state);
        means->ptr.p_double[j] = mean;
        sigmas->ptr.p_double[j] = ae_sqrt(variance, _state);
        if( ae_fp_eq(sigmas->ptr.p_double[j],0) )
        {
            sigmas->ptr.p_double[j] = 1;
        }
    }
    ae_frame_leave(_state);
}


/*************************************************************************

  -- ALGLIB --
     Copyright 19.05.2008 by Bochkanov Sergey
*************************************************************************/
double dsgetmeanmindistance(/* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_int_t nvars,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t i;
    ae_int_t j;
    ae_vector tmp;
    ae_vector tmp2;
    double v;
    double result;

    ae_frame_make(_state, &_frame_block);
    ae_vector_init(&tmp, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&tmp2, 0, DT_REAL, _state, ae_true);

    
    /*
     * Test parameters
     */
    if( npoints<=0||nvars<1 )
    {
        result = 0;
        ae_frame_leave(_state);
        return result;
    }
    
    /*
     * Process
     */
    ae_vector_set_length(&tmp, npoints-1+1, _state);
    for(i=0; i<=npoints-1; i++)
    {
        tmp.ptr.p_double[i] = ae_maxrealnumber;
    }
    ae_vector_set_length(&tmp2, nvars-1+1, _state);
    for(i=0; i<=npoints-1; i++)
    {
        for(j=i+1; j<=npoints-1; j++)
        {
            ae_v_move(&tmp2.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1));
            ae_v_sub(&tmp2.ptr.p_double[0], 1, &xy->ptr.pp_double[j][0], 1, ae_v_len(0,nvars-1));
            v = ae_v_dotproduct(&tmp2.ptr.p_double[0], 1, &tmp2.ptr.p_double[0], 1, ae_v_len(0,nvars-1));
            v = ae_sqrt(v, _state);
            tmp.ptr.p_double[i] = ae_minreal(tmp.ptr.p_double[i], v, _state);
            tmp.ptr.p_double[j] = ae_minreal(tmp.ptr.p_double[j], v, _state);
        }
    }
    result = 0;
    for(i=0; i<=npoints-1; i++)
    {
        result = result+tmp.ptr.p_double[i]/npoints;
    }
    ae_frame_leave(_state);
    return result;
}


/*************************************************************************

  -- ALGLIB --
     Copyright 19.05.2008 by Bochkanov Sergey
*************************************************************************/
void dstie(/* Real    */ ae_vector* a,
     ae_int_t n,
     /* Integer */ ae_vector* ties,
     ae_int_t* tiecount,
     /* Integer */ ae_vector* p1,
     /* Integer */ ae_vector* p2,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t i;
    ae_int_t k;
    ae_vector tmp;

    ae_frame_make(_state, &_frame_block);
    ae_vector_clear(ties);
    *tiecount = 0;
    ae_vector_clear(p1);
    ae_vector_clear(p2);
    ae_vector_init(&tmp, 0, DT_INT, _state, ae_true);

    
    /*
     * Special case
     */
    if( n<=0 )
    {
        *tiecount = 0;
        ae_frame_leave(_state);
        return;
    }
    
    /*
     * Sort A
     */
    tagsort(a, n, p1, p2, _state);
    
    /*
     * Process ties
     */
    *tiecount = 1;
    for(i=1; i<=n-1; i++)
    {
        if( ae_fp_neq(a->ptr.p_double[i],a->ptr.p_double[i-1]) )
        {
            *tiecount = *tiecount+1;
        }
    }
    ae_vector_set_length(ties, *tiecount+1, _state);
    ties->ptr.p_int[0] = 0;
    k = 1;
    for(i=1; i<=n-1; i++)
    {
        if( ae_fp_neq(a->ptr.p_double[i],a->ptr.p_double[i-1]) )
        {
            ties->ptr.p_int[k] = i;
            k = k+1;
        }
    }
    ties->ptr.p_int[*tiecount] = n;
    ae_frame_leave(_state);
}


/*************************************************************************

  -- ALGLIB --
     Copyright 11.12.2008 by Bochkanov Sergey
*************************************************************************/
void dstiefasti(/* Real    */ ae_vector* a,
     /* Integer */ ae_vector* b,
     ae_int_t n,
     /* Integer */ ae_vector* ties,
     ae_int_t* tiecount,
     /* Real    */ ae_vector* bufr,
     /* Integer */ ae_vector* bufi,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t i;
    ae_int_t k;
    ae_vector tmp;

    ae_frame_make(_state, &_frame_block);
    *tiecount = 0;
    ae_vector_init(&tmp, 0, DT_INT, _state, ae_true);

    
    /*
     * Special case
     */
    if( n<=0 )
    {
        *tiecount = 0;
        ae_frame_leave(_state);
        return;
    }
    
    /*
     * Sort A
     */
    tagsortfasti(a, b, bufr, bufi, n, _state);
    
    /*
     * Process ties
     */
    ties->ptr.p_int[0] = 0;
    k = 1;
    for(i=1; i<=n-1; i++)
    {
        if( ae_fp_neq(a->ptr.p_double[i],a->ptr.p_double[i-1]) )
        {
            ties->ptr.p_int[k] = i;
            k = k+1;
        }
    }
    ties->ptr.p_int[k] = n;
    *tiecount = k;
    ae_frame_leave(_state);
}


/*************************************************************************
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

  -- ALGLIB --
     Copyright 22.05.2008 by Bochkanov Sergey
*************************************************************************/
void dsoptimalsplit2(/* Real    */ ae_vector* a,
     /* Integer */ ae_vector* c,
     ae_int_t n,
     ae_int_t* info,
     double* threshold,
     double* pal,
     double* pbl,
     double* par,
     double* pbr,
     double* cve,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_vector _a;
    ae_vector _c;
    ae_int_t i;
    ae_int_t t;
    double s;
    ae_vector ties;
    ae_int_t tiecount;
    ae_vector p1;
    ae_vector p2;
    ae_int_t k;
    ae_int_t koptimal;
    double pak;
    double pbk;
    double cvoptimal;
    double cv;

    ae_frame_make(_state, &_frame_block);
    ae_vector_init_copy(&_a, a, _state, ae_true);
    a = &_a;
    ae_vector_init_copy(&_c, c, _state, ae_true);
    c = &_c;
    *info = 0;
    *threshold = 0;
    *pal = 0;
    *pbl = 0;
    *par = 0;
    *pbr = 0;
    *cve = 0;
    ae_vector_init(&ties, 0, DT_INT, _state, ae_true);
    ae_vector_init(&p1, 0, DT_INT, _state, ae_true);
    ae_vector_init(&p2, 0, DT_INT, _state, ae_true);

    
    /*
     * Test for errors in inputs
     */
    if( n<=0 )
    {
        *info = -1;
        ae_frame_leave(_state);
        return;
    }
    for(i=0; i<=n-1; i++)
    {
        if( c->ptr.p_int[i]!=0&&c->ptr.p_int[i]!=1 )
        {
            *info = -2;
            ae_frame_leave(_state);
            return;
        }
    }
    *info = 1;
    
    /*
     * Tie
     */
    dstie(a, n, &ties, &tiecount, &p1, &p2, _state);
    for(i=0; i<=n-1; i++)
    {
        if( p2.ptr.p_int[i]!=i )
        {
            t = c->ptr.p_int[i];
            c->ptr.p_int[i] = c->ptr.p_int[p2.ptr.p_int[i]];
            c->ptr.p_int[p2.ptr.p_int[i]] = t;
        }
    }
    
    /*
     * Special case: number of ties is 1.
     *
     * NOTE: we assume that P[i,j] equals to 0 or 1,
     *       intermediate values are not allowed.
     */
    if( tiecount==1 )
    {
        *info = -3;
        ae_frame_leave(_state);
        return;
    }
    
    /*
     * General case, number of ties > 1
     *
     * NOTE: we assume that P[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-1; i++)
    {
        if( c->ptr.p_int[i]==0 )
        {
            *par = *par+1;
        }
        if( c->ptr.p_int[i]==1 )
        {
            *pbr = *pbr+1;
        }
    }
    koptimal = -1;
    cvoptimal = ae_maxrealnumber;
    for(k=0; k<=tiecount-2; k++)
    {
        
        /*
         * first, obtain information about K-th tie which is
         * moved from R-part to L-part
         */
        pak = 0;
        pbk = 0;
        for(i=ties.ptr.p_int[k]; i<=ties.ptr.p_int[k+1]-1; i++)
        {
            if( c->ptr.p_int[i]==0 )
            {
                pak = pak+1;
            }
            if( c->ptr.p_int[i]==1 )
            {
                pbk = pbk+1;
            }
        }
        
        /*
         * Calculate cross-validation CE
         */
        cv = 0;
        cv = cv-bdss_xlny(*pal+pak, (*pal+pak)/(*pal+pak+(*pbl)+pbk+1), _state);
        cv = cv-bdss_xlny(*pbl+pbk, (*pbl+pbk)/(*pal+pak+1+(*pbl)+pbk), _state);
        cv = cv-bdss_xlny(*par-pak, (*par-pak)/(*par-pak+(*pbr)-pbk+1), _state);
        cv = cv-bdss_xlny(*pbr-pbk, (*pbr-pbk)/(*par-pak+1+(*pbr)-pbk), _state);
        
        /*
         * Compare with best
         */
        if( ae_fp_less(cv,cvoptimal) )
        {
            cvoptimal = cv;
            koptimal = k;
        }
        
        /*
         * update
         */
        *pal = *pal+pak;
        *pbl = *pbl+pbk;
        *par = *par-pak;
        *pbr = *pbr-pbk;
    }
    *cve = cvoptimal;
    *threshold = 0.5*(a->ptr.p_double[ties.ptr.p_int[koptimal]]+a->ptr.p_double[ties.ptr.p_int[koptimal+1]]);
    *pal = 0;
    *pbl = 0;
    *par = 0;
    *pbr = 0;
    for(i=0; i<=n-1; i++)
    {
        if( ae_fp_less(a->ptr.p_double[i],*threshold) )
        {
            if( c->ptr.p_int[i]==0 )
            {
                *pal = *pal+1;
            }
            else
            {
                *pbl = *pbl+1;
            }
        }
        else
        {
            if( c->ptr.p_int[i]==0 )
            {
                *par = *par+1;
            }
            else
            {
                *pbr = *pbr+1;
            }
        }
    }
    s = *pal+(*pbl);
    *pal = *pal/s;
    *pbl = *pbl/s;
    s = *par+(*pbr);
    *par = *par/s;
    *pbr = *pbr/s;
    ae_frame_leave(_state);
}


/*************************************************************************
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.

  -- ALGLIB --
     Copyright 11.12.2008 by Bochkanov Sergey
*************************************************************************/
void dsoptimalsplit2fast(/* Real    */ ae_vector* a,
     /* Integer */ ae_vector* c,
     /* Integer */ ae_vector* tiesbuf,
     /* Integer */ ae_vector* cntbuf,
     /* Real    */ ae_vector* bufr,
     /* Integer */ ae_vector* bufi,
     ae_int_t n,
     ae_int_t nc,
     double alpha,
     ae_int_t* info,
     double* threshold,
     double* rms,
     double* cvrms,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t k;
    ae_int_t cl;
    ae_int_t tiecount;
    double cbest;
    double cc;
    ae_int_t koptimal;
    ae_int_t sl;
    ae_int_t sr;
    double v;
    double w;
    double x;

    *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-1; i++)
    {
        if( c->ptr.p_int[i]<0||c->ptr.p_int[i]>=nc )
        {
            *info = -2;
            return;
        }
    }
    *info = 1;
    
    /*
     * Tie
     */
    dstiefasti(a, c, n, tiesbuf, &tiecount, bufr, bufi, _state);
    
    /*
     * Special case: number of ties is 1.
     */
    if( tiecount==1 )
    {
        *info = -3;
        return;
    }
    
    /*
     * General case, number of ties > 1
     */
    for(i=0; i<=2*nc-1; i++)
    {
        cntbuf->ptr.p_int[i] = 0;
    }
    for(i=0; i<=n-1; i++)
    {
        cntbuf->ptr.p_int[nc+c->ptr.p_int[i]] = cntbuf->ptr.p_int[nc+c->ptr.p_int[i]]+1;
    }
    koptimal = -1;
    *threshold = a->ptr.p_double[n-1];
    cbest = ae_maxrealnumber;
    sl = 0;
    sr = n;
    for(k=0; k<=tiecount-2; k++)
    {
        
        /*
         * first, move Kth tie from right to left
         */
        for(i=tiesbuf->ptr.p_int[k]; i<=tiesbuf->ptr.p_int[k+1]-1; i++)
        {
            cl = c->ptr.p_int[i];
            cntbuf->ptr.p_int[cl] = cntbuf->ptr.p_int[cl]+1;
            cntbuf->ptr.p_int[nc+cl] = cntbuf->ptr.p_int[nc+cl]-1;
        }
        sl = sl+(tiesbuf->ptr.p_int[k+1]-tiesbuf->ptr.p_int[k]);
        sr = sr-(tiesbuf->ptr.p_int[k+1]-tiesbuf->ptr.p_int[k]);
        
        /*
         * Calculate RMS error
         */
        v = 0;
        for(i=0; i<=nc-1; i++)
        {
            w = cntbuf->ptr.p_int[i];
            v = v+w*ae_sqr(w/sl-1, _state);
            v = v+(sl-w)*ae_sqr(w/sl, _state);
            w = cntbuf->ptr.p_int[nc+i];
            v = v+w*ae_sqr(w/sr-1, _state);
            v = v+(sr-w)*ae_sqr(w/sr, _state);
        }
        v = ae_sqrt(v/(nc*n), _state);
        
        /*
         * Compare with best
         */
        x = (double)(2*sl)/(double)(sl+sr)-1;
        cc = v*(1-alpha+alpha*ae_sqr(x, _state));
        if( ae_fp_less(cc,cbest) )
        {
            
            /*
             * store split
             */
            *rms = v;
            koptimal = k;
            cbest = cc;
            
            /*
             * calculate CVRMS error
             */
            *cvrms = 0;
            for(i=0; i<=nc-1; i++)
            {
                if( sl>1 )
                {
                    w = cntbuf->ptr.p_int[i];
                    *cvrms = *cvrms+w*ae_sqr((w-1)/(sl-1)-1, _state);
                    *cvrms = *cvrms+(sl-w)*ae_sqr(w/(sl-1), _state);
                }
                else
                {
                    w = cntbuf->ptr.p_int[i];
                    *cvrms = *cvrms+w*ae_sqr((double)1/(double)nc-1, _state);
                    *cvrms = *cvrms+(sl-w)*ae_sqr((double)1/(double)nc, _state);
                }
                if( sr>1 )
                {
                    w = cntbuf->ptr.p_int[nc+i];
                    *cvrms = *cvrms+w*ae_sqr((w-1)/(sr-1)-1, _state);
                    *cvrms = *cvrms+(sr-w)*ae_sqr(w/(sr-1), _state);
                }
                else
                {
                    w = cntbuf->ptr.p_int[nc+i];
                    *cvrms = *cvrms+w*ae_sqr((double)1/(double)nc-1, _state);
                    *cvrms = *cvrms+(sr-w)*ae_sqr((double)1/(double)nc, _state);
                }
            }
            *cvrms = ae_sqrt(*cvrms/(nc*n), _state);
        }
    }
    
    /*
     * 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->ptr.p_double[tiesbuf->ptr.p_int[koptimal]]+a->ptr.p_double[tiesbuf->ptr.p_int[koptimal+1]]);
    if( ae_fp_less_eq(*threshold,a->ptr.p_double[tiesbuf->ptr.p_int[koptimal]]) )
    {
        *threshold = a->ptr.p_double[tiesbuf->ptr.p_int[koptimal+1]];
    }
}


/*************************************************************************
Automatic non-optimal discretization, internal subroutine.

  -- ALGLIB --
     Copyright 22.05.2008 by Bochkanov Sergey
*************************************************************************/
void dssplitk(/* Real    */ ae_vector* a,
     /* Integer */ ae_vector* c,
     ae_int_t n,
     ae_int_t nc,
     ae_int_t kmax,
     ae_int_t* info,
     /* Real    */ ae_vector* thresholds,
     ae_int_t* ni,
     double* cve,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_vector _a;
    ae_vector _c;
    ae_int_t i;
    ae_int_t j;
    ae_int_t j1;
    ae_int_t k;
    ae_vector ties;
    ae_int_t tiecount;
    ae_vector p1;
    ae_vector p2;
    ae_vector cnt;
    double v2;
    ae_int_t bestk;
    double bestcve;
    ae_vector bestsizes;
    double curcve;
    ae_vector cursizes;

    ae_frame_make(_state, &_frame_block);
    ae_vector_init_copy(&_a, a, _state, ae_true);
    a = &_a;
    ae_vector_init_copy(&_c, c, _state, ae_true);
    c = &_c;
    *info = 0;
    ae_vector_clear(thresholds);
    *ni = 0;
    *cve = 0;
    ae_vector_init(&ties, 0, DT_INT, _state, ae_true);
    ae_vector_init(&p1, 0, DT_INT, _state, ae_true);
    ae_vector_init(&p2, 0, DT_INT, _state, ae_true);
    ae_vector_init(&cnt, 0, DT_INT, _state, ae_true);
    ae_vector_init(&bestsizes, 0, DT_INT, _state, ae_true);
    ae_vector_init(&cursizes, 0, DT_INT, _state, ae_true);

    
    /*
     * Test for errors in inputs
     */
    if( (n<=0||nc<2)||kmax<2 )
    {
        *info = -1;
        ae_frame_leave(_state);
        return;
    }
    for(i=0; i<=n-1; i++)
    {
        if( c->ptr.p_int[i]<0||c->ptr.p_int[i]>=nc )
        {
            *info = -2;
            ae_frame_leave(_state);
            return;
        }
    }
    *info = 1;
    
    /*
     * Tie
     */
    dstie(a, n, &ties, &tiecount, &p1, &p2, _state);
    for(i=0; i<=n-1; i++)
    {
        if( p2.ptr.p_int[i]!=i )
        {
            k = c->ptr.p_int[i];
            c->ptr.p_int[i] = c->ptr.p_int[p2.ptr.p_int[i]];
            c->ptr.p_int[p2.ptr.p_int[i]] = k;
        }
    }
    
    /*
     * Special cases
     */
    if( tiecount==1 )
    {
        *info = -3;
        ae_frame_leave(_state);
        return;
    }
    
    /*
     * General case:
     * 0. allocate arrays
     */
    kmax = ae_minint(kmax, tiecount, _state);
    ae_vector_set_length(&bestsizes, kmax-1+1, _state);
    ae_vector_set_length(&cursizes, kmax-1+1, _state);
    ae_vector_set_length(&cnt, nc-1+1, _state);
    
    /*
     * General case:
     * 1. prepare "weak" solution (two subintervals, divided at median)
     */
    v2 = ae_maxrealnumber;
    j = -1;
    for(i=1; i<=tiecount-1; i++)
    {
        if( ae_fp_less(ae_fabs(ties.ptr.p_int[i]-0.5*(n-1), _state),v2) )
        {
            v2 = ae_fabs(ties.ptr.p_int[i]-0.5*n, _state);
            j = i;
        }
    }
    ae_assert(j>0, "DSSplitK: internal error #1!", _state);
    bestk = 2;
    bestsizes.ptr.p_int[0] = ties.ptr.p_int[j];
    bestsizes.ptr.p_int[1] = n-j;
    bestcve = 0;
    for(i=0; i<=nc-1; i++)
    {
        cnt.ptr.p_int[i] = 0;
    }
    for(i=0; i<=j-1; i++)
    {
        bdss_tieaddc(c, &ties, i, nc, &cnt, _state);
    }
    bestcve = bestcve+bdss_getcv(&cnt, nc, _state);
    for(i=0; i<=nc-1; i++)
    {
        cnt.ptr.p_int[i] = 0;
    }
    for(i=j; i<=tiecount-1; i++)
    {
        bdss_tieaddc(c, &ties, i, nc, &cnt, _state);
    }
    bestcve = bestcve+bdss_getcv(&cnt, nc, _state);
    
    /*
     * 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
         */
        for(i=0; i<=k-1; i++)
        {
            cursizes.ptr.p_int[i] = 0;
        }
        i = 0;
        j = 0;
        while(j<=tiecount-1&&i<=k-1)
        {
            
            /*
             * Rule: I-th bin is empty, fill it
             */
            if( cursizes.ptr.p_int[i]==0 )
            {
                cursizes.ptr.p_int[i] = ties.ptr.p_int[j+1]-ties.ptr.p_int[j];
                j = j+1;
                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 = i+1;
                continue;
            }
            
            /*
             * Rule: last bin, always place in current
             */
            if( i==k-1 )
            {
                cursizes.ptr.p_int[i] = cursizes.ptr.p_int[i]+ties.ptr.p_int[j+1]-ties.ptr.p_int[j];
                j = j+1;
                continue;
            }
            
            /*
             * Place J-th tie in I-th bin, or leave for I+1-th bin.
             */
            if( ae_fp_less(ae_fabs(cursizes.ptr.p_int[i]+ties.ptr.p_int[j+1]-ties.ptr.p_int[j]-(double)n/(double)k, _state),ae_fabs(cursizes.ptr.p_int[i]-(double)n/(double)k, _state)) )
            {
                cursizes.ptr.p_int[i] = cursizes.ptr.p_int[i]+ties.ptr.p_int[j+1]-ties.ptr.p_int[j];
                j = j+1;
            }
            else
            {
                i = i+1;
            }
        }
        ae_assert(cursizes.ptr.p_int[k-1]!=0&&j==tiecount, "DSSplitK: internal error #1", _state);
        
        /*
         * Calculate CVE
         */
        curcve = 0;
        j = 0;
        for(i=0; i<=k-1; i++)
        {
            for(j1=0; j1<=nc-1; j1++)
            {
                cnt.ptr.p_int[j1] = 0;
            }
            for(j1=j; j1<=j+cursizes.ptr.p_int[i]-1; j1++)
            {
                cnt.ptr.p_int[c->ptr.p_int[j1]] = cnt.ptr.p_int[c->ptr.p_int[j1]]+1;
            }
            curcve = curcve+bdss_getcv(&cnt, nc, _state);
            j = j+cursizes.ptr.p_int[i];
        }
        
        /*
         * Choose best variant
         */
        if( ae_fp_less(curcve,bestcve) )
        {
            for(i=0; i<=k-1; i++)
            {
                bestsizes.ptr.p_int[i] = cursizes.ptr.p_int[i];
            }
            bestcve = curcve;
            bestk = k;
        }
    }
    
    /*
     * Transform from sizes to thresholds
     */
    *cve = bestcve;
    *ni = bestk;
    ae_vector_set_length(thresholds, *ni-2+1, _state);
    j = bestsizes.ptr.p_int[0];
    for(i=1; i<=bestk-1; i++)
    {
        thresholds->ptr.p_double[i-1] = 0.5*(a->ptr.p_double[j-1]+a->ptr.p_double[j]);
        j = j+bestsizes.ptr.p_int[i];
    }
    ae_frame_leave(_state);
}


/*************************************************************************
Automatic optimal discretization, internal subroutine.

  -- ALGLIB --
     Copyright 22.05.2008 by Bochkanov Sergey
*************************************************************************/
void dsoptimalsplitk(/* Real    */ ae_vector* a,
     /* Integer */ ae_vector* c,
     ae_int_t n,
     ae_int_t nc,
     ae_int_t kmax,
     ae_int_t* info,
     /* Real    */ ae_vector* thresholds,
     ae_int_t* ni,
     double* cve,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_vector _a;
    ae_vector _c;
    ae_int_t i;
    ae_int_t j;
    ae_int_t s;
    ae_int_t jl;
    ae_int_t jr;
    double v2;
    ae_vector ties;
    ae_int_t tiecount;
    ae_vector p1;
    ae_vector p2;
    double cvtemp;
    ae_vector cnt;
    ae_vector cnt2;
    ae_matrix cv;
    ae_matrix splits;
    ae_int_t k;
    ae_int_t koptimal;
    double cvoptimal;

    ae_frame_make(_state, &_frame_block);
    ae_vector_init_copy(&_a, a, _state, ae_true);
    a = &_a;
    ae_vector_init_copy(&_c, c, _state, ae_true);
    c = &_c;
    *info = 0;
    ae_vector_clear(thresholds);
    *ni = 0;
    *cve = 0;
    ae_vector_init(&ties, 0, DT_INT, _state, ae_true);
    ae_vector_init(&p1, 0, DT_INT, _state, ae_true);
    ae_vector_init(&p2, 0, DT_INT, _state, ae_true);
    ae_vector_init(&cnt, 0, DT_INT, _state, ae_true);
    ae_vector_init(&cnt2, 0, DT_INT, _state, ae_true);
    ae_matrix_init(&cv, 0, 0, DT_REAL, _state, ae_true);
    ae_matrix_init(&splits, 0, 0, DT_INT, _state, ae_true);

    
    /*
     * Test for errors in inputs
     */
    if( (n<=0||nc<2)||kmax<2 )
    {
        *info = -1;
        ae_frame_leave(_state);
        return;
    }
    for(i=0; i<=n-1; i++)
    {
        if( c->ptr.p_int[i]<0||c->ptr.p_int[i]>=nc )
        {
            *info = -2;
            ae_frame_leave(_state);
            return;
        }
    }
    *info = 1;
    
    /*
     * Tie
     */
    dstie(a, n, &ties, &tiecount, &p1, &p2, _state);
    for(i=0; i<=n-1; i++)
    {
        if( p2.ptr.p_int[i]!=i )
        {
            k = c->ptr.p_int[i];
            c->ptr.p_int[i] = c->ptr.p_int[p2.ptr.p_int[i]];
            c->ptr.p_int[p2.ptr.p_int[i]] = k;
        }
    }
    
    /*
     * Special cases
     */
    if( tiecount==1 )
    {
        *info = -3;
        ae_frame_leave(_state);
        return;
    }
    
    /*
     * General case
     * Use dynamic programming to find best split in O(KMax*NC*TieCount^2) time
     */
    kmax = ae_minint(kmax, tiecount, _state);
    ae_matrix_set_length(&cv, kmax-1+1, tiecount-1+1, _state);
    ae_matrix_set_length(&splits, kmax-1+1, tiecount-1+1, _state);
    ae_vector_set_length(&cnt, nc-1+1, _state);
    ae_vector_set_length(&cnt2, nc-1+1, _state);
    for(j=0; j<=nc-1; j++)
    {
        cnt.ptr.p_int[j] = 0;
    }
    for(j=0; j<=tiecount-1; j++)
    {
        bdss_tieaddc(c, &ties, j, nc, &cnt, _state);
        splits.ptr.pp_int[0][j] = 0;
        cv.ptr.pp_double[0][j] = bdss_getcv(&cnt, nc, _state);
    }
    for(k=1; k<=kmax-1; k++)
    {
        for(j=0; j<=nc-1; j++)
        {
            cnt.ptr.p_int[j] = 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
             */
            bdss_tieaddc(c, &ties, j, nc, &cnt, _state);
            
            /*
             * Search for optimal split point S in [K..J]
             */
            for(i=0; i<=nc-1; i++)
            {
                cnt2.ptr.p_int[i] = cnt.ptr.p_int[i];
            }
            cv.ptr.pp_double[k][j] = cv.ptr.pp_double[k-1][j-1]+bdss_getcv(&cnt2, nc, _state);
            splits.ptr.pp_int[k][j] = j;
            for(s=k+1; s<=j; s++)
            {
                
                /*
                 * Update Cnt2 - let it contain classes of ties from S-th to J-th
                 */
                bdss_tiesubc(c, &ties, s-1, nc, &cnt2, _state);
                
                /*
                 * Calculate CVE
                 */
                cvtemp = cv.ptr.pp_double[k-1][s-1]+bdss_getcv(&cnt2, nc, _state);
                if( ae_fp_less(cvtemp,cv.ptr.pp_double[k][j]) )
                {
                    cv.ptr.pp_double[k][j] = cvtemp;
                    splits.ptr.pp_int[k][j] = s;
                }
            }
        }
    }
    
    /*
     * Choose best partition, output result
     */
    koptimal = -1;
    cvoptimal = ae_maxrealnumber;
    for(k=0; k<=kmax-1; k++)
    {
        if( ae_fp_less(cv.ptr.pp_double[k][tiecount-1],cvoptimal) )
        {
            cvoptimal = cv.ptr.pp_double[k][tiecount-1];
            koptimal = k;
        }
    }
    ae_assert(koptimal>=0, "DSOptimalSplitK: internal error #1!", _state);
    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 = ae_maxrealnumber;
        j = -1;
        for(i=1; i<=tiecount-1; i++)
        {
            if( ae_fp_less(ae_fabs(ties.ptr.p_int[i]-0.5*(n-1), _state),v2) )
            {
                v2 = ae_fabs(ties.ptr.p_int[i]-0.5*(n-1), _state);
                j = i;
            }
        }
        ae_assert(j>0, "DSOptimalSplitK: internal error #2!", _state);
        ae_vector_set_length(thresholds, 0+1, _state);
        thresholds->ptr.p_double[0] = 0.5*(a->ptr.p_double[ties.ptr.p_int[j-1]]+a->ptr.p_double[ties.ptr.p_int[j]]);
        *ni = 2;
        *cve = 0;
        for(i=0; i<=nc-1; i++)
        {
            cnt.ptr.p_int[i] = 0;
        }
        for(i=0; i<=j-1; i++)
        {
            bdss_tieaddc(c, &ties, i, nc, &cnt, _state);
        }
        *cve = *cve+bdss_getcv(&cnt, nc, _state);
        for(i=0; i<=nc-1; i++)
        {
            cnt.ptr.p_int[i] = 0;
        }
        for(i=j; i<=tiecount-1; i++)
        {
            bdss_tieaddc(c, &ties, i, nc, &cnt, _state);
        }
        *cve = *cve+bdss_getcv(&cnt, nc, _state);
    }
    else
    {
        
        /*
         * General case: 2 or more intervals
         */
        ae_vector_set_length(thresholds, koptimal-1+1, _state);
        *ni = koptimal+1;
        *cve = cv.ptr.pp_double[koptimal][tiecount-1];
        jl = splits.ptr.pp_int[koptimal][tiecount-1];
        jr = tiecount-1;
        for(k=koptimal; k>=1; k--)
        {
            thresholds->ptr.p_double[k-1] = 0.5*(a->ptr.p_double[ties.ptr.p_int[jl-1]]+a->ptr.p_double[ties.ptr.p_int[jl]]);
            jr = jl-1;
            jl = splits.ptr.pp_int[k-1][jl-1];
        }
    }
    ae_frame_leave(_state);
}


/*************************************************************************
Internal function
*************************************************************************/
static double bdss_xlny(double x, double y, ae_state *_state)
{
    double result;


    if( ae_fp_eq(x,0) )
    {
        result = 0;
    }
    else
    {
        result = x*ae_log(y, _state);
    }
    return result;
}


/*************************************************************************
Internal function,
returns number of samples of class I in Cnt[I]
*************************************************************************/
static double bdss_getcv(/* Integer */ ae_vector* cnt,
     ae_int_t nc,
     ae_state *_state)
{
    ae_int_t i;
    double s;
    double result;


    s = 0;
    for(i=0; i<=nc-1; i++)
    {
        s = s+cnt->ptr.p_int[i];
    }
    result = 0;
    for(i=0; i<=nc-1; i++)
    {
        result = result-bdss_xlny(cnt->ptr.p_int[i], cnt->ptr.p_int[i]/(s+nc-1), _state);
    }
    return result;
}


/*************************************************************************
Internal function, adds number of samples of class I in tie NTie to Cnt[I]
*************************************************************************/
static void bdss_tieaddc(/* Integer */ ae_vector* c,
     /* Integer */ ae_vector* ties,
     ae_int_t ntie,
     ae_int_t nc,
     /* Integer */ ae_vector* cnt,
     ae_state *_state)
{
    ae_int_t i;


    for(i=ties->ptr.p_int[ntie]; i<=ties->ptr.p_int[ntie+1]-1; i++)
    {
        cnt->ptr.p_int[c->ptr.p_int[i]] = cnt->ptr.p_int[c->ptr.p_int[i]]+1;
    }
}


/*************************************************************************
Internal function, subtracts number of samples of class I in tie NTie to Cnt[I]
*************************************************************************/
static void bdss_tiesubc(/* Integer */ ae_vector* c,
     /* Integer */ ae_vector* ties,
     ae_int_t ntie,
     ae_int_t nc,
     /* Integer */ ae_vector* cnt,
     ae_state *_state)
{
    ae_int_t i;


    for(i=ties->ptr.p_int[ntie]; i<=ties->ptr.p_int[ntie+1]-1; i++)
    {
        cnt->ptr.p_int[c->ptr.p_int[i]] = cnt->ptr.p_int[c->ptr.p_int[i]]-1;
    }
}


ae_bool _cvreport_init(void* _p, ae_state *_state, ae_bool make_automatic)
{
    cvreport *p = (cvreport*)_p;
    ae_touch_ptr((void*)p);
    return ae_true;
}


ae_bool _cvreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic)
{
    cvreport *dst = (cvreport*)_dst;
    cvreport *src = (cvreport*)_src;
    dst->relclserror = src->relclserror;
    dst->avgce = src->avgce;
    dst->rmserror = src->rmserror;
    dst->avgerror = src->avgerror;
    dst->avgrelerror = src->avgrelerror;
    return ae_true;
}


void _cvreport_clear(void* _p)
{
    cvreport *p = (cvreport*)_p;
    ae_touch_ptr((void*)p);
}


void _cvreport_destroy(void* _p)
{
    cvreport *p = (cvreport*)_p;
    ae_touch_ptr((void*)p);
}




/*************************************************************************
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

  -- ALGLIB --
     Copyright 10.07.2012 by Bochkanov Sergey
*************************************************************************/
void clusterizercreate(clusterizerstate* s, ae_state *_state)
{

    _clusterizerstate_clear(s);

    s->npoints = 0;
    s->nfeatures = 0;
    s->disttype = 2;
    s->ahcalgo = 0;
    s->kmeansrestarts = 1;
    s->kmeansmaxits = 0;
}


/*************************************************************************
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)
                * 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.
       
  -- ALGLIB --
     Copyright 10.07.2012 by Bochkanov Sergey
*************************************************************************/
void clusterizersetpoints(clusterizerstate* s,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_int_t nfeatures,
     ae_int_t disttype,
     ae_state *_state)
{
    ae_int_t i;


    ae_assert((((((((disttype==0||disttype==1)||disttype==2)||disttype==10)||disttype==11)||disttype==12)||disttype==13)||disttype==20)||disttype==21, "ClusterizerSetPoints: incorrect DistType", _state);
    ae_assert(npoints>=0, "ClusterizerSetPoints: NPoints<0", _state);
    ae_assert(nfeatures>=1, "ClusterizerSetPoints: NFeatures<1", _state);
    ae_assert(xy->rows>=npoints, "ClusterizerSetPoints: Rows(XY)<NPoints", _state);
    ae_assert(xy->cols>=nfeatures, "ClusterizerSetPoints: Cols(XY)<NFeatures", _state);
    ae_assert(apservisfinitematrix(xy, npoints, nfeatures, _state), "ClusterizerSetPoints: XY contains NAN/INF", _state);
    s->npoints = npoints;
    s->nfeatures = nfeatures;
    s->disttype = disttype;
    rmatrixsetlengthatleast(&s->xy, npoints, nfeatures, _state);
    for(i=0; i<=npoints-1; i++)
    {
        ae_v_move(&s->xy.ptr.pp_double[i][0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nfeatures-1));
    }
}


/*************************************************************************
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.

  -- ALGLIB --
     Copyright 10.07.2012 by Bochkanov Sergey
*************************************************************************/
void clusterizersetdistances(clusterizerstate* s,
     /* Real    */ ae_matrix* d,
     ae_int_t npoints,
     ae_bool isupper,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t j;
    ae_int_t j0;
    ae_int_t j1;


    ae_assert(npoints>=0, "ClusterizerSetDistances: NPoints<0", _state);
    ae_assert(d->rows>=npoints, "ClusterizerSetDistances: Rows(D)<NPoints", _state);
    ae_assert(d->cols>=npoints, "ClusterizerSetDistances: Cols(D)<NPoints", _state);
    s->npoints = npoints;
    s->nfeatures = 0;
    s->disttype = -1;
    rmatrixsetlengthatleast(&s->d, npoints, npoints, _state);
    for(i=0; i<=npoints-1; i++)
    {
        if( isupper )
        {
            j0 = i+1;
            j1 = npoints-1;
        }
        else
        {
            j0 = 0;
            j1 = i-1;
        }
        for(j=j0; j<=j1; j++)
        {
            ae_assert(ae_isfinite(d->ptr.pp_double[i][j], _state)&&ae_fp_greater_eq(d->ptr.pp_double[i][j],0), "ClusterizerSetDistances: D contains infinite, NAN or negative elements", _state);
            s->d.ptr.pp_double[i][j] = d->ptr.pp_double[i][j];
            s->d.ptr.pp_double[j][i] = d->ptr.pp_double[i][j];
        }
        s->d.ptr.pp_double[i][i] = 0;
    }
}


/*************************************************************************
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

  -- ALGLIB --
     Copyright 10.07.2012 by Bochkanov Sergey
*************************************************************************/
void clusterizersetahcalgo(clusterizerstate* s,
     ae_int_t algo,
     ae_state *_state)
{


    ae_assert(((algo==0||algo==1)||algo==2)||algo==3, "ClusterizerSetHCAlgo: incorrect algorithm type", _state);
    s->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.

  -- ALGLIB --
     Copyright 10.07.2012 by Bochkanov Sergey
*************************************************************************/
void clusterizersetkmeanslimits(clusterizerstate* s,
     ae_int_t restarts,
     ae_int_t maxits,
     ae_state *_state)
{


    ae_assert(restarts>=1, "ClusterizerSetKMeansLimits: Restarts<=0", _state);
    ae_assert(maxits>=0, "ClusterizerSetKMeansLimits: MaxIts<0", _state);
    s->kmeansrestarts = restarts;
    s->kmeansmaxits = maxits;
}


/*************************************************************************
This function performs agglomerative hierarchical clustering

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.

  -- ALGLIB --
     Copyright 10.07.2012 by Bochkanov Sergey
*************************************************************************/
void clusterizerrunahc(clusterizerstate* s,
     ahcreport* rep,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t npoints;
    ae_int_t nfeatures;
    ae_matrix d;

    ae_frame_make(_state, &_frame_block);
    _ahcreport_clear(rep);
    ae_matrix_init(&d, 0, 0, DT_REAL, _state, ae_true);

    npoints = s->npoints;
    nfeatures = s->nfeatures;
    
    /*
     * Fill Rep.NPoints, quick exit when NPoints<=1
     */
    rep->npoints = npoints;
    if( npoints==0 )
    {
        ae_vector_set_length(&rep->p, 0, _state);
        ae_matrix_set_length(&rep->z, 0, 0, _state);
        ae_matrix_set_length(&rep->pz, 0, 0, _state);
        ae_matrix_set_length(&rep->pm, 0, 0, _state);
        ae_vector_set_length(&rep->mergedist, 0, _state);
        ae_frame_leave(_state);
        return;
    }
    if( npoints==1 )
    {
        ae_vector_set_length(&rep->p, 1, _state);
        ae_matrix_set_length(&rep->z, 0, 0, _state);
        ae_matrix_set_length(&rep->pz, 0, 0, _state);
        ae_matrix_set_length(&rep->pm, 0, 0, _state);
        ae_vector_set_length(&rep->mergedist, 0, _state);
        rep->p.ptr.p_int[0] = 0;
        ae_frame_leave(_state);
        return;
    }
    
    /*
     * More than one point
     */
    if( s->disttype==-1 )
    {
        
        /*
         * Run clusterizer with user-supplied distance matrix
         */
        clustering_clusterizerrunahcinternal(s, &s->d, rep, _state);
        ae_frame_leave(_state);
        return;
    }
    else
    {
        
        /*
         * Build distance matrix D.
         */
        clusterizergetdistances(&s->xy, npoints, nfeatures, s->disttype, &d, _state);
        
        /*
         * Run clusterizer
         */
        clustering_clusterizerrunahcinternal(s, &d, rep, _state);
        ae_frame_leave(_state);
        return;
    }
    ae_frame_leave(_state);
}


/*************************************************************************
This function performs clustering by k-means++ algorithm.

You may change algorithm properties like number of restarts or  iterations
limit by calling ClusterizerSetKMeansLimits() functions.

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).

  -- ALGLIB --
     Copyright 10.07.2012 by Bochkanov Sergey
*************************************************************************/
void clusterizerrunkmeans(clusterizerstate* s,
     ae_int_t k,
     kmeansreport* rep,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_matrix dummy;

    ae_frame_make(_state, &_frame_block);
    _kmeansreport_clear(rep);
    ae_matrix_init(&dummy, 0, 0, DT_REAL, _state, ae_true);

    ae_assert(k>=0, "ClusterizerRunKMeans: K<0", _state);
    
    /*
     * Incorrect distance type
     */
    if( s->disttype!=2 )
    {
        rep->npoints = s->npoints;
        rep->terminationtype = -5;
        rep->k = k;
        ae_frame_leave(_state);
        return;
    }
    
    /*
     * K>NPoints or (K=0 and NPoints>0)
     */
    if( k>s->npoints||(k==0&&s->npoints>0) )
    {
        rep->npoints = s->npoints;
        rep->terminationtype = -3;
        rep->k = k;
        ae_frame_leave(_state);
        return;
    }
    
    /*
     * No points
     */
    if( s->npoints==0 )
    {
        rep->npoints = 0;
        rep->terminationtype = 1;
        rep->k = k;
        ae_frame_leave(_state);
        return;
    }
    
    /*
     * Normal case:
     * 1<=K<=NPoints, Euclidean distance 
     */
    rep->npoints = s->npoints;
    rep->nfeatures = s->nfeatures;
    rep->k = k;
    rep->npoints = s->npoints;
    rep->nfeatures = s->nfeatures;
    kmeansgenerateinternal(&s->xy, s->npoints, s->nfeatures, k, s->kmeansmaxits, s->kmeansrestarts, &rep->terminationtype, &dummy, ae_false, &rep->c, ae_true, &rep->cidx, _state);
    ae_frame_leave(_state);
}


/*************************************************************************
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)
                * 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)

NOTES: 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.

  -- ALGLIB --
     Copyright 10.07.2012 by Bochkanov Sergey
*************************************************************************/
void clusterizergetdistances(/* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_int_t nfeatures,
     ae_int_t disttype,
     /* Real    */ ae_matrix* d,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t i;
    ae_int_t j;
    ae_int_t k;
    double v;
    double vv;
    ae_matrix tmpxy;
    ae_vector tmpx;
    ae_vector tmpy;
    apbuffers buf;

    ae_frame_make(_state, &_frame_block);
    ae_matrix_clear(d);
    ae_matrix_init(&tmpxy, 0, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&tmpx, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&tmpy, 0, DT_REAL, _state, ae_true);
    _apbuffers_init(&buf, _state, ae_true);

    ae_assert(nfeatures>=1, "ClusterizerGetDistances: NFeatures<1", _state);
    ae_assert(npoints>=0, "ClusterizerGetDistances: NPoints<1", _state);
    ae_assert((((((((disttype==0||disttype==1)||disttype==2)||disttype==10)||disttype==11)||disttype==12)||disttype==13)||disttype==20)||disttype==21, "ClusterizerGetDistances: incorrect DistType", _state);
    ae_assert(xy->rows>=npoints, "ClusterizerGetDistances: Rows(XY)<NPoints", _state);
    ae_assert(xy->cols>=nfeatures, "ClusterizerGetDistances: Cols(XY)<NFeatures", _state);
    ae_assert(apservisfinitematrix(xy, npoints, nfeatures, _state), "ClusterizerGetDistances: XY contains NAN/INF", _state);
    
    /*
     * Quick exit
     */
    if( npoints==0 )
    {
        ae_frame_leave(_state);
        return;
    }
    if( npoints==1 )
    {
        ae_matrix_set_length(d, 1, 1, _state);
        d->ptr.pp_double[0][0] = 0;
        ae_frame_leave(_state);
        return;
    }
    
    /*
     * Build distance matrix D.
     */
    if( disttype==0 )
    {
        
        /*
         * Chebyshev distance
         */
        ae_matrix_set_length(d, npoints, npoints, _state);
        for(i=0; i<=npoints-1; i++)
        {
            d->ptr.pp_double[i][i] = 0.0;
            for(j=i+1; j<=npoints-1; j++)
            {
                v = 0.0;
                for(k=0; k<=nfeatures-1; k++)
                {
                    vv = xy->ptr.pp_double[i][k]-xy->ptr.pp_double[j][k];
                    if( ae_fp_less(vv,0) )
                    {
                        vv = -vv;
                    }
                    if( ae_fp_greater(vv,v) )
                    {
                        v = vv;
                    }
                }
                d->ptr.pp_double[i][j] = v;
                d->ptr.pp_double[j][i] = v;
            }
        }
        ae_frame_leave(_state);
        return;
    }
    if( disttype==1 )
    {
        
        /*
         * City block distance
         */
        ae_matrix_set_length(d, npoints, npoints, _state);
        for(i=0; i<=npoints-1; i++)
        {
            d->ptr.pp_double[i][i] = 0.0;
            for(j=i+1; j<=npoints-1; j++)
            {
                v = 0.0;
                for(k=0; k<=nfeatures-1; k++)
                {
                    vv = xy->ptr.pp_double[i][k]-xy->ptr.pp_double[j][k];
                    if( ae_fp_less(vv,0) )
                    {
                        vv = -vv;
                    }
                    v = v+vv;
                }
                v = v/nfeatures;
                d->ptr.pp_double[i][j] = v;
                d->ptr.pp_double[j][i] = v;
            }
        }
        ae_frame_leave(_state);
        return;
    }
    if( disttype==2 )
    {
        
        /*
         * Euclidean distance
         */
        ae_matrix_set_length(d, npoints, npoints, _state);
        ae_matrix_set_length(&tmpxy, npoints, nfeatures, _state);
        ae_vector_set_length(&tmpx, nfeatures, _state);
        for(j=0; j<=nfeatures-1; j++)
        {
            tmpx.ptr.p_double[j] = 0.0;
        }
        v = (double)1/(double)npoints;
        for(i=0; i<=npoints-1; i++)
        {
            ae_v_addd(&tmpx.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nfeatures-1), v);
        }
        for(i=0; i<=npoints-1; i++)
        {
            ae_v_move(&tmpxy.ptr.pp_double[i][0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nfeatures-1));
            ae_v_sub(&tmpxy.ptr.pp_double[i][0], 1, &tmpx.ptr.p_double[0], 1, ae_v_len(0,nfeatures-1));
        }
        rmatrixsyrk(npoints, nfeatures, 1.0, &tmpxy, 0, 0, 0, 0.0, d, 0, 0, ae_true, _state);
        for(i=0; i<=npoints-1; i++)
        {
            for(j=i+1; j<=npoints-1; j++)
            {
                v = ae_sqrt(ae_maxreal(d->ptr.pp_double[i][i]+d->ptr.pp_double[j][j]-2*d->ptr.pp_double[i][j], 0.0, _state), _state);
                d->ptr.pp_double[i][j] = v;
                d->ptr.pp_double[j][i] = v;
            }
        }
        for(i=0; i<=npoints-1; i++)
        {
            d->ptr.pp_double[i][i] = 0.0;
        }
        ae_frame_leave(_state);
        return;
    }
    if( disttype==10||disttype==11 )
    {
        
        /*
         * Absolute/nonabsolute Pearson correlation distance
         */
        ae_matrix_set_length(d, npoints, npoints, _state);
        ae_matrix_set_length(&tmpxy, nfeatures, npoints, _state);
        for(i=0; i<=npoints-1; i++)
        {
            ae_v_move(&tmpxy.ptr.pp_double[0][i], tmpxy.stride, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nfeatures-1));
        }
        pearsoncorrm(&tmpxy, nfeatures, npoints, d, _state);
        for(i=0; i<=npoints-1; i++)
        {
            for(j=i+1; j<=npoints-1; j++)
            {
                if( disttype==10 )
                {
                    v = 1-d->ptr.pp_double[i][j];
                }
                else
                {
                    v = 1-ae_fabs(d->ptr.pp_double[i][j], _state);
                }
                v = ae_maxreal(v, 0.0, _state);
                d->ptr.pp_double[i][j] = v;
                d->ptr.pp_double[j][i] = v;
            }
        }
        for(i=0; i<=npoints-1; i++)
        {
            d->ptr.pp_double[i][i] = 0.0;
        }
        ae_frame_leave(_state);
        return;
    }
    if( disttype==12||disttype==13 )
    {
        
        /*
         * Absolute/nonabsolute uncentered Pearson correlation distance
         */
        ae_matrix_set_length(d, npoints, npoints, _state);
        rmatrixsyrk(npoints, nfeatures, 1.0, xy, 0, 0, 0, 0.0, d, 0, 0, ae_true, _state);
        for(i=0; i<=npoints-1; i++)
        {
            for(j=i+1; j<=npoints-1; j++)
            {
                v = d->ptr.pp_double[i][j]/ae_sqrt(d->ptr.pp_double[i][i]*d->ptr.pp_double[j][j], _state);
                if( disttype==13 )
                {
                    v = ae_fabs(v, _state);
                }
                v = ae_minreal(v, 1.0, _state);
                d->ptr.pp_double[i][j] = 1-v;
                d->ptr.pp_double[j][i] = 1-v;
            }
        }
        for(i=0; i<=npoints-1; i++)
        {
            d->ptr.pp_double[i][i] = 0.0;
        }
        ae_frame_leave(_state);
        return;
    }
    if( disttype==20||disttype==21 )
    {
        
        /*
         * Spearman rank correlation
         */
        ae_matrix_set_length(d, npoints, npoints, _state);
        ae_vector_set_length(&tmpx, nfeatures, _state);
        ae_vector_set_length(&tmpy, nfeatures, _state);
        ae_matrix_set_length(&tmpxy, nfeatures, npoints, _state);
        for(i=0; i<=npoints-1; i++)
        {
            ae_v_move(&tmpx.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nfeatures-1));
            rankx(&tmpx, nfeatures, &buf, _state);
            ae_v_move(&tmpxy.ptr.pp_double[0][i], tmpxy.stride, &tmpx.ptr.p_double[0], 1, ae_v_len(0,nfeatures-1));
        }
        pearsoncorrm(&tmpxy, nfeatures, npoints, d, _state);
        for(i=0; i<=npoints-1; i++)
        {
            for(j=i+1; j<=npoints-1; j++)
            {
                if( disttype==20 )
                {
                    v = 1-d->ptr.pp_double[i][j];
                }
                else
                {
                    v = 1-ae_fabs(d->ptr.pp_double[i][j], _state);
                }
                v = ae_maxreal(v, 0.0, _state);
                d->ptr.pp_double[i][j] = v;
                d->ptr.pp_double[j][i] = v;
            }
        }
        for(i=0; i<=npoints-1; i++)
        {
            d->ptr.pp_double[i][i] = 0.0;
        }
        ae_frame_leave(_state);
        return;
    }
    ae_assert(ae_false, "Assertion failed", _state);
    ae_frame_leave(_state);
}


/*************************************************************************
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))).

  -- ALGLIB --
     Copyright 10.07.2012 by Bochkanov Sergey
*************************************************************************/
void clusterizergetkclusters(ahcreport* rep,
     ae_int_t k,
     /* Integer */ ae_vector* cidx,
     /* Integer */ ae_vector* cz,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t i;
    ae_int_t mergeidx;
    ae_int_t i0;
    ae_int_t i1;
    ae_int_t t;
    ae_vector presentclusters;
    ae_vector clusterindexes;
    ae_vector clustersizes;
    ae_vector tmpidx;
    ae_int_t npoints;

    ae_frame_make(_state, &_frame_block);
    ae_vector_clear(cidx);
    ae_vector_clear(cz);
    ae_vector_init(&presentclusters, 0, DT_BOOL, _state, ae_true);
    ae_vector_init(&clusterindexes, 0, DT_INT, _state, ae_true);
    ae_vector_init(&clustersizes, 0, DT_INT, _state, ae_true);
    ae_vector_init(&tmpidx, 0, DT_INT, _state, ae_true);

    npoints = rep->npoints;
    ae_assert(npoints>=0, "ClusterizerGetKClusters: internal error in Rep integrity", _state);
    ae_assert(k>=0, "ClusterizerGetKClusters: K<=0", _state);
    ae_assert(k<=npoints, "ClusterizerGetKClusters: K>NPoints", _state);
    ae_assert(k>0||npoints==0, "ClusterizerGetKClusters: K<=0", _state);
    ae_assert(npoints==rep->npoints, "ClusterizerGetKClusters: NPoints<>Rep.NPoints", _state);
    
    /*
     * Quick exit
     */
    if( npoints==0 )
    {
        ae_frame_leave(_state);
        return;
    }
    if( npoints==1 )
    {
        ae_vector_set_length(cz, 1, _state);
        ae_vector_set_length(cidx, 1, _state);
        cz->ptr.p_int[0] = 0;
        cidx->ptr.p_int[0] = 0;
        ae_frame_leave(_state);
        return;
    }
    
    /*
     * Replay merges, from top to bottom,
     * keep track of clusters being present at the moment
     */
    ae_vector_set_length(&presentclusters, 2*npoints-1, _state);
    ae_vector_set_length(&tmpidx, npoints, _state);
    for(i=0; i<=2*npoints-3; i++)
    {
        presentclusters.ptr.p_bool[i] = ae_false;
    }
    presentclusters.ptr.p_bool[2*npoints-2] = ae_true;
    for(i=0; i<=npoints-1; i++)
    {
        tmpidx.ptr.p_int[i] = 2*npoints-2;
    }
    for(mergeidx=npoints-2; mergeidx>=npoints-k; mergeidx--)
    {
        
        /*
         * Update information about clusters being present at the moment
         */
        presentclusters.ptr.p_bool[npoints+mergeidx] = ae_false;
        presentclusters.ptr.p_bool[rep->z.ptr.pp_int[mergeidx][0]] = ae_true;
        presentclusters.ptr.p_bool[rep->z.ptr.pp_int[mergeidx][1]] = ae_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->pm.ptr.pp_int[mergeidx][0];
        i1 = rep->pm.ptr.pp_int[mergeidx][1];
        t = rep->z.ptr.pp_int[mergeidx][0];
        for(i=i0; i<=i1; i++)
        {
            tmpidx.ptr.p_int[i] = t;
        }
        i0 = rep->pm.ptr.pp_int[mergeidx][2];
        i1 = rep->pm.ptr.pp_int[mergeidx][3];
        t = rep->z.ptr.pp_int[mergeidx][1];
        for(i=i0; i<=i1; i++)
        {
            tmpidx.ptr.p_int[i] = t;
        }
    }
    
    /*
     * Fill CZ - array which allows us to convert cluster indexes
     * from one system to another.
     */
    ae_vector_set_length(cz, k, _state);
    ae_vector_set_length(&clusterindexes, 2*npoints-1, _state);
    t = 0;
    for(i=0; i<=2*npoints-2; i++)
    {
        if( presentclusters.ptr.p_bool[i] )
        {
            cz->ptr.p_int[t] = i;
            clusterindexes.ptr.p_int[i] = t;
            t = t+1;
        }
    }
    ae_assert(t==k, "ClusterizerGetKClusters: internal error", _state);
    
    /*
     * Convert indexes stored in CIdx
     */
    ae_vector_set_length(cidx, npoints, _state);
    for(i=0; i<=npoints-1; i++)
    {
        cidx->ptr.p_int[i] = clusterindexes.ptr.p_int[tmpidx.ptr.p_int[rep->p.ptr.p_int[i]]];
    }
    ae_frame_leave(_state);
}


/*************************************************************************
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))).

  -- ALGLIB --
     Copyright 10.07.2012 by Bochkanov Sergey
*************************************************************************/
void clusterizerseparatedbydist(ahcreport* rep,
     double r,
     ae_int_t* k,
     /* Integer */ ae_vector* cidx,
     /* Integer */ ae_vector* cz,
     ae_state *_state)
{

    *k = 0;
    ae_vector_clear(cidx);
    ae_vector_clear(cz);

    ae_assert(ae_isfinite(r, _state)&&ae_fp_greater_eq(r,0), "ClusterizerSeparatedByDist: R is infinite or less than 0", _state);
    *k = 1;
    while(*k<rep->npoints&&ae_fp_greater_eq(rep->mergedist.ptr.p_double[rep->npoints-1-(*k)],r))
    {
        *k = *k+1;
    }
    clusterizergetkclusters(rep, *k, cidx, cz, _state);
}


/*************************************************************************
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))).

  -- ALGLIB --
     Copyright 10.07.2012 by Bochkanov Sergey
*************************************************************************/
void clusterizerseparatedbycorr(ahcreport* rep,
     double r,
     ae_int_t* k,
     /* Integer */ ae_vector* cidx,
     /* Integer */ ae_vector* cz,
     ae_state *_state)
{

    *k = 0;
    ae_vector_clear(cidx);
    ae_vector_clear(cz);

    ae_assert((ae_isfinite(r, _state)&&ae_fp_greater_eq(r,-1))&&ae_fp_less_eq(r,1), "ClusterizerSeparatedByCorr: R is infinite or less than 0", _state);
    *k = 1;
    while(*k<rep->npoints&&ae_fp_greater_eq(rep->mergedist.ptr.p_double[rep->npoints-1-(*k)],1-r))
    {
        *k = *k+1;
    }
    clusterizergetkclusters(rep, *k, cidx, cz, _state);
}


/*************************************************************************
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
    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

  -- ALGLIB --
     Copyright 21.03.2009 by Bochkanov Sergey
*************************************************************************/
void kmeansgenerateinternal(/* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_int_t nvars,
     ae_int_t k,
     ae_int_t maxits,
     ae_int_t restarts,
     ae_int_t* info,
     /* Real    */ ae_matrix* ccol,
     ae_bool needccol,
     /* Real    */ ae_matrix* crow,
     ae_bool needcrow,
     /* Integer */ ae_vector* xyc,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t i;
    ae_int_t j;
    ae_matrix ct;
    ae_matrix ctbest;
    ae_vector xycbest;
    double e;
    double eprev;
    double ebest;
    ae_vector x;
    ae_vector tmp;
    ae_vector d2;
    ae_vector p;
    ae_vector csizes;
    ae_vector cbusy;
    double v;
    ae_int_t cclosest;
    double dclosest;
    ae_vector work;
    ae_bool waschanges;
    ae_bool zerosizeclusters;
    ae_int_t pass;
    ae_int_t itcnt;

    ae_frame_make(_state, &_frame_block);
    *info = 0;
    ae_matrix_clear(ccol);
    ae_matrix_clear(crow);
    ae_vector_clear(xyc);
    ae_matrix_init(&ct, 0, 0, DT_REAL, _state, ae_true);
    ae_matrix_init(&ctbest, 0, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&xycbest, 0, DT_INT, _state, ae_true);
    ae_vector_init(&x, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&tmp, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&d2, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&p, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&csizes, 0, DT_INT, _state, ae_true);
    ae_vector_init(&cbusy, 0, DT_BOOL, _state, ae_true);
    ae_vector_init(&work, 0, DT_REAL, _state, ae_true);

    
    /*
     * Test parameters
     */
    if( ((npoints<k||nvars<1)||k<1)||restarts<1 )
    {
        *info = -1;
        ae_frame_leave(_state);
        return;
    }
    
    /*
     * TODO: special case K=1
     * TODO: special case K=NPoints
     */
    *info = 1;
    
    /*
     * Multiple passes of k-means++ algorithm
     */
    ae_matrix_set_length(&ct, k, nvars, _state);
    ae_matrix_set_length(&ctbest, k, nvars, _state);
    ae_vector_set_length(xyc, npoints, _state);
    ae_vector_set_length(&xycbest, npoints, _state);
    ae_vector_set_length(&d2, npoints, _state);
    ae_vector_set_length(&p, npoints, _state);
    ae_vector_set_length(&tmp, nvars, _state);
    ae_vector_set_length(&csizes, k, _state);
    ae_vector_set_length(&cbusy, k, _state);
    ebest = ae_maxrealnumber;
    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 = ae_randominteger(npoints, _state);
        ae_v_move(&ct.ptr.pp_double[0][0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1));
        cbusy.ptr.p_bool[0] = ae_true;
        for(i=1; i<=k-1; i++)
        {
            cbusy.ptr.p_bool[i] = ae_false;
        }
        if( !clustering_selectcenterpp(xy, npoints, nvars, &ct, &cbusy, k, &d2, &p, &tmp, _state) )
        {
            *info = -3;
            ae_frame_leave(_state);
            return;
        }
        
        /*
         * Update centers:
         * 2. update center positions
         */
        for(i=0; i<=npoints-1; i++)
        {
            xyc->ptr.p_int[i] = -1;
        }
        eprev = ae_maxrealnumber;
        itcnt = 0;
        e = 0;
        while(maxits==0||itcnt<maxits)
        {
            
            /*
             * Update iteration counter
             */
            itcnt = itcnt+1;
            
            /*
             * fill XYC with center numbers
             */
            waschanges = ae_false;
            for(i=0; i<=npoints-1; i++)
            {
                cclosest = -1;
                dclosest = ae_maxrealnumber;
                for(j=0; j<=k-1; j++)
                {
                    ae_v_move(&tmp.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1));
                    ae_v_sub(&tmp.ptr.p_double[0], 1, &ct.ptr.pp_double[j][0], 1, ae_v_len(0,nvars-1));
                    v = ae_v_dotproduct(&tmp.ptr.p_double[0], 1, &tmp.ptr.p_double[0], 1, ae_v_len(0,nvars-1));
                    if( ae_fp_less(v,dclosest) )
                    {
                        cclosest = j;
                        dclosest = v;
                    }
                }
                if( xyc->ptr.p_int[i]!=cclosest )
                {
                    waschanges = ae_true;
                }
                xyc->ptr.p_int[i] = cclosest;
            }
            
            /*
             * Update centers
             */
            for(j=0; j<=k-1; j++)
            {
                csizes.ptr.p_int[j] = 0;
            }
            for(i=0; i<=k-1; i++)
            {
                for(j=0; j<=nvars-1; j++)
                {
                    ct.ptr.pp_double[i][j] = 0;
                }
            }
            for(i=0; i<=npoints-1; i++)
            {
                csizes.ptr.p_int[xyc->ptr.p_int[i]] = csizes.ptr.p_int[xyc->ptr.p_int[i]]+1;
                ae_v_add(&ct.ptr.pp_double[xyc->ptr.p_int[i]][0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1));
            }
            zerosizeclusters = ae_false;
            for(j=0; j<=k-1; j++)
            {
                if( csizes.ptr.p_int[j]!=0 )
                {
                    v = (double)1/(double)csizes.ptr.p_int[j];
                    ae_v_muld(&ct.ptr.pp_double[j][0], 1, ae_v_len(0,nvars-1), v);
                }
                cbusy.ptr.p_bool[j] = csizes.ptr.p_int[j]!=0;
                zerosizeclusters = zerosizeclusters||csizes.ptr.p_int[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
                 */
                if( !clustering_selectcenterpp(xy, npoints, nvars, &ct, &cbusy, k, &d2, &p, &tmp, _state) )
                {
                    *info = -3;
                    ae_frame_leave(_state);
                    return;
                }
                continue;
            }
            
            /*
             * Stop if one of two conditions is met:
             * 1. nothing has changed during iteration
             * 2. energy function increased 
             */
            e = 0;
            for(i=0; i<=npoints-1; i++)
            {
                ae_v_move(&tmp.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1));
                ae_v_sub(&tmp.ptr.p_double[0], 1, &ct.ptr.pp_double[xyc->ptr.p_int[i]][0], 1, ae_v_len(0,nvars-1));
                v = ae_v_dotproduct(&tmp.ptr.p_double[0], 1, &tmp.ptr.p_double[0], 1, ae_v_len(0,nvars-1));
                e = e+v;
            }
            if( !waschanges||ae_fp_greater_eq(e,eprev) )
            {
                break;
            }
            
            /*
             * Update EPrev
             */
            eprev = e;
        }
        
        /*
         * 3. Calculate E, compare with best centers found so far
         */
        if( ae_fp_less(e,ebest) )
        {
            
            /*
             * store partition.
             */
            ebest = e;
            copymatrix(&ct, 0, k-1, 0, nvars-1, &ctbest, 0, k-1, 0, nvars-1, _state);
            for(i=0; i<=npoints-1; i++)
            {
                xycbest.ptr.p_int[i] = xyc->ptr.p_int[i];
            }
        }
    }
    
    /*
     * Copy and transpose
     */
    if( needccol )
    {
        ae_matrix_set_length(ccol, nvars, k, _state);
        copyandtranspose(&ctbest, 0, k-1, 0, nvars-1, ccol, 0, nvars-1, 0, k-1, _state);
    }
    if( needcrow )
    {
        ae_matrix_set_length(crow, k, nvars, _state);
        rmatrixcopy(k, nvars, &ctbest, 0, 0, crow, 0, 0, _state);
    }
    for(i=0; i<=npoints-1; i++)
    {
        xyc->ptr.p_int[i] = xycbest.ptr.p_int[i];
    }
    ae_frame_leave(_state);
}


/*************************************************************************
Select center for a new cluster using k-means++ rule
*************************************************************************/
static ae_bool clustering_selectcenterpp(/* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_int_t nvars,
     /* Real    */ ae_matrix* centers,
     /* Boolean */ ae_vector* busycenters,
     ae_int_t ccnt,
     /* Real    */ ae_vector* d2,
     /* Real    */ ae_vector* p,
     /* Real    */ ae_vector* tmp,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t j;
    ae_int_t cc;
    double v;
    double s;
    ae_bool result;


    result = ae_true;
    for(cc=0; cc<=ccnt-1; cc++)
    {
        if( !busycenters->ptr.p_bool[cc] )
        {
            
            /*
             * fill D2
             */
            for(i=0; i<=npoints-1; i++)
            {
                d2->ptr.p_double[i] = ae_maxrealnumber;
                for(j=0; j<=ccnt-1; j++)
                {
                    if( busycenters->ptr.p_bool[j] )
                    {
                        ae_v_move(&tmp->ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1));
                        ae_v_sub(&tmp->ptr.p_double[0], 1, &centers->ptr.pp_double[j][0], 1, ae_v_len(0,nvars-1));
                        v = ae_v_dotproduct(&tmp->ptr.p_double[0], 1, &tmp->ptr.p_double[0], 1, ae_v_len(0,nvars-1));
                        if( ae_fp_less(v,d2->ptr.p_double[i]) )
                        {
                            d2->ptr.p_double[i] = v;
                        }
                    }
                }
            }
            
            /*
             * calculate P (non-cumulative)
             */
            s = 0;
            for(i=0; i<=npoints-1; i++)
            {
                s = s+d2->ptr.p_double[i];
            }
            if( ae_fp_eq(s,0) )
            {
                result = ae_false;
                return result;
            }
            s = 1/s;
            ae_v_moved(&p->ptr.p_double[0], 1, &d2->ptr.p_double[0], 1, ae_v_len(0,npoints-1), s);
            
            /*
             * 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 = ae_randomreal(_state);
            for(i=0; i<=npoints-1; i++)
            {
                s = s+p->ptr.p_double[i];
                if( ae_fp_less_eq(v,s)||i==npoints-1 )
                {
                    ae_v_move(&centers->ptr.pp_double[cc][0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1));
                    busycenters->ptr.p_bool[cc] = ae_true;
                    break;
                }
            }
        }
    }
    return result;
}


/*************************************************************************
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.NFeatures,S.NFeatures]
                Contents of the matrix is destroyed during
                algorithm operation.

OUTPUT PARAMETERS:
    Rep     -   clustering results; see description of AHCReport
                structure for more information.

  -- ALGLIB --
     Copyright 10.07.2012 by Bochkanov Sergey
*************************************************************************/
static void clustering_clusterizerrunahcinternal(clusterizerstate* s,
     /* Real    */ ae_matrix* d,
     ahcreport* rep,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t i;
    ae_int_t j;
    ae_int_t k;
    double v;
    ae_int_t mergeidx;
    ae_int_t c0;
    ae_int_t c1;
    ae_int_t s0;
    ae_int_t s1;
    ae_int_t ar;
    ae_int_t br;
    ae_int_t npoints;
    ae_vector cidx;
    ae_vector csizes;
    ae_vector nnidx;
    ae_matrix cinfo;

    ae_frame_make(_state, &_frame_block);
    ae_vector_init(&cidx, 0, DT_INT, _state, ae_true);
    ae_vector_init(&csizes, 0, DT_INT, _state, ae_true);
    ae_vector_init(&nnidx, 0, DT_INT, _state, ae_true);
    ae_matrix_init(&cinfo, 0, 0, DT_INT, _state, ae_true);

    npoints = s->npoints;
    
    /*
     * Fill Rep.NPoints, quick exit when NPoints<=1
     */
    rep->npoints = npoints;
    if( npoints==0 )
    {
        ae_vector_set_length(&rep->p, 0, _state);
        ae_matrix_set_length(&rep->z, 0, 0, _state);
        ae_matrix_set_length(&rep->pz, 0, 0, _state);
        ae_matrix_set_length(&rep->pm, 0, 0, _state);
        ae_vector_set_length(&rep->mergedist, 0, _state);
        ae_frame_leave(_state);
        return;
    }
    if( npoints==1 )
    {
        ae_vector_set_length(&rep->p, 1, _state);
        ae_matrix_set_length(&rep->z, 0, 0, _state);
        ae_matrix_set_length(&rep->pz, 0, 0, _state);
        ae_matrix_set_length(&rep->pm, 0, 0, _state);
        ae_vector_set_length(&rep->mergedist, 0, _state);
        rep->p.ptr.p_int[0] = 0;
        ae_frame_leave(_state);
        return;
    }
    ae_matrix_set_length(&rep->z, npoints-1, 2, _state);
    ae_vector_set_length(&rep->mergedist, npoints-1, _state);
    
    /*
     * Build list of nearest neighbors
     */
    ae_vector_set_length(&nnidx, npoints, _state);
    for(i=0; i<=npoints-1; i++)
    {
        
        /*
         * Calculate index of the nearest neighbor
         */
        k = -1;
        v = ae_maxrealnumber;
        for(j=0; j<=npoints-1; j++)
        {
            if( j!=i&&ae_fp_less(d->ptr.pp_double[i][j],v) )
            {
                k = j;
                v = d->ptr.pp_double[i][j];
            }
        }
        ae_assert(ae_fp_less(v,ae_maxrealnumber), "ClusterizerRunAHC: internal error", _state);
        nnidx.ptr.p_int[i] = k;
    }
    
    /*
     * 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.
     *         
     */
    ae_vector_set_length(&cidx, npoints, _state);
    ae_vector_set_length(&csizes, npoints, _state);
    for(i=0; i<=npoints-1; i++)
    {
        cidx.ptr.p_int[i] = i;
        csizes.ptr.p_int[i] = 1;
    }
    for(mergeidx=0; mergeidx<=npoints-2; mergeidx++)
    {
        
        /*
         * Select pair of clusters (C0,C1) with CIdx[C0]<CIdx[C1] to merge.
         */
        c0 = -1;
        c1 = -1;
        v = ae_maxrealnumber;
        for(i=0; i<=npoints-1; i++)
        {
            if( cidx.ptr.p_int[i]>=0 )
            {
                if( ae_fp_less(d->ptr.pp_double[i][nnidx.ptr.p_int[i]],v) )
                {
                    c0 = i;
                    c1 = nnidx.ptr.p_int[i];
                    v = d->ptr.pp_double[i][nnidx.ptr.p_int[i]];
                }
            }
        }
        ae_assert(ae_fp_less(v,ae_maxrealnumber), "ClusterizerRunAHC: internal error", _state);
        if( cidx.ptr.p_int[c0]>cidx.ptr.p_int[c1] )
        {
            i = c1;
            c1 = c0;
            c0 = i;
        }
        
        /*
         * Fill one row of Rep.Z and one element of Rep.MergeDist
         */
        rep->z.ptr.pp_int[mergeidx][0] = cidx.ptr.p_int[c0];
        rep->z.ptr.pp_int[mergeidx][1] = cidx.ptr.p_int[c1];
        rep->mergedist.ptr.p_double[mergeidx] = v;
        
        /*
         * 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.
         */
        ae_assert(((s->ahcalgo==0||s->ahcalgo==1)||s->ahcalgo==2)||s->ahcalgo==3, "ClusterizerRunAHC: internal error", _state);
        for(i=0; i<=npoints-1; i++)
        {
            if( i!=c0&&i!=c1 )
            {
                if( s->ahcalgo==0 )
                {
                    d->ptr.pp_double[i][c0] = ae_maxreal(d->ptr.pp_double[i][c0], d->ptr.pp_double[i][c1], _state);
                }
                if( s->ahcalgo==1 )
                {
                    d->ptr.pp_double[i][c0] = ae_minreal(d->ptr.pp_double[i][c0], d->ptr.pp_double[i][c1], _state);
                }
                if( s->ahcalgo==2 )
                {
                    d->ptr.pp_double[i][c0] = (csizes.ptr.p_int[c0]*d->ptr.pp_double[i][c0]+csizes.ptr.p_int[c1]*d->ptr.pp_double[i][c1])/(csizes.ptr.p_int[c0]+csizes.ptr.p_int[c1]);
                }
                if( s->ahcalgo==3 )
                {
                    d->ptr.pp_double[i][c0] = (d->ptr.pp_double[i][c0]+d->ptr.pp_double[i][c1])/2;
                }
                d->ptr.pp_double[c0][i] = d->ptr.pp_double[i][c0];
            }
        }
        
        /*
         * Update CIdx and CSizes
         */
        cidx.ptr.p_int[c0] = npoints+mergeidx;
        cidx.ptr.p_int[c1] = -1;
        csizes.ptr.p_int[c0] = csizes.ptr.p_int[c0]+csizes.ptr.p_int[c1];
        csizes.ptr.p_int[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-1; i++)
        {
            if( (cidx.ptr.p_int[i]>=0&&i!=c0)&&(nnidx.ptr.p_int[i]==c0||nnidx.ptr.p_int[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->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.ptr.p_int[i] = c0;
                }
                else
                {
                    
                    /*
                     * Something other than single linkage. We have to re-examine
                     * all the row to find nearest neighbor.
                     */
                    k = -1;
                    v = ae_maxrealnumber;
                    for(j=0; j<=npoints-1; j++)
                    {
                        if( (cidx.ptr.p_int[j]>=0&&j!=i)&&ae_fp_less(d->ptr.pp_double[i][j],v) )
                        {
                            k = j;
                            v = d->ptr.pp_double[i][j];
                        }
                    }
                    ae_assert(ae_fp_less(v,ae_maxrealnumber)||mergeidx==npoints-2, "ClusterizerRunAHC: internal error", _state);
                    nnidx.ptr.p_int[i] = k;
                }
            }
        }
        k = -1;
        v = ae_maxrealnumber;
        for(j=0; j<=npoints-1; j++)
        {
            if( (cidx.ptr.p_int[j]>=0&&j!=c0)&&ae_fp_less(d->ptr.pp_double[c0][j],v) )
            {
                k = j;
                v = d->ptr.pp_double[c0][j];
            }
        }
        ae_assert(ae_fp_less(v,ae_maxrealnumber)||mergeidx==npoints-2, "ClusterizerRunAHC: internal error", _state);
        nnidx.ptr.p_int[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.
     */
    ae_vector_set_length(&rep->p, npoints, _state);
    ae_matrix_set_length(&rep->pm, npoints-1, 6, _state);
    ae_matrix_set_length(&cinfo, 2*npoints-1, 4, _state);
    for(i=0; i<=npoints-1; i++)
    {
        cinfo.ptr.pp_int[i][0] = 1;
        cinfo.ptr.pp_int[i][3] = 0;
    }
    for(i=0; i<=npoints-2; i++)
    {
        cinfo.ptr.pp_int[npoints+i][0] = cinfo.ptr.pp_int[rep->z.ptr.pp_int[i][0]][0]+cinfo.ptr.pp_int[rep->z.ptr.pp_int[i][1]][0];
        cinfo.ptr.pp_int[npoints+i][3] = ae_maxint(cinfo.ptr.pp_int[rep->z.ptr.pp_int[i][0]][3], cinfo.ptr.pp_int[rep->z.ptr.pp_int[i][1]][3], _state)+1;
    }
    cinfo.ptr.pp_int[2*npoints-2][1] = 0;
    cinfo.ptr.pp_int[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->z.ptr.pp_int[i][0];
        c1 = rep->z.ptr.pp_int[i][1];
        s0 = cinfo.ptr.pp_int[c0][0];
        s1 = cinfo.ptr.pp_int[c1][0];
        ar = cinfo.ptr.pp_int[npoints+i][1];
        br = cinfo.ptr.pp_int[npoints+i][2];
        cinfo.ptr.pp_int[c0][1] = ar;
        cinfo.ptr.pp_int[c0][2] = ar+s0-1;
        cinfo.ptr.pp_int[c1][1] = br-(s1-1);
        cinfo.ptr.pp_int[c1][2] = br;
        rep->pm.ptr.pp_int[i][0] = cinfo.ptr.pp_int[c0][1];
        rep->pm.ptr.pp_int[i][1] = cinfo.ptr.pp_int[c0][2];
        rep->pm.ptr.pp_int[i][2] = cinfo.ptr.pp_int[c1][1];
        rep->pm.ptr.pp_int[i][3] = cinfo.ptr.pp_int[c1][2];
        rep->pm.ptr.pp_int[i][4] = cinfo.ptr.pp_int[c0][3];
        rep->pm.ptr.pp_int[i][5] = cinfo.ptr.pp_int[c1][3];
    }
    for(i=0; i<=npoints-1; i++)
    {
        ae_assert(cinfo.ptr.pp_int[i][1]==cinfo.ptr.pp_int[i][2], "Assertion failed", _state);
        rep->p.ptr.p_int[i] = cinfo.ptr.pp_int[i][1];
    }
    
    /*
     * Calculate Rep.PZ
     */
    ae_matrix_set_length(&rep->pz, npoints-1, 2, _state);
    for(i=0; i<=npoints-2; i++)
    {
        rep->pz.ptr.pp_int[i][0] = rep->z.ptr.pp_int[i][0];
        rep->pz.ptr.pp_int[i][1] = rep->z.ptr.pp_int[i][1];
        if( rep->pz.ptr.pp_int[i][0]<npoints )
        {
            rep->pz.ptr.pp_int[i][0] = rep->p.ptr.p_int[rep->pz.ptr.pp_int[i][0]];
        }
        if( rep->pz.ptr.pp_int[i][1]<npoints )
        {
            rep->pz.ptr.pp_int[i][1] = rep->p.ptr.p_int[rep->pz.ptr.pp_int[i][1]];
        }
    }
    ae_frame_leave(_state);
}


ae_bool _clusterizerstate_init(void* _p, ae_state *_state, ae_bool make_automatic)
{
    clusterizerstate *p = (clusterizerstate*)_p;
    ae_touch_ptr((void*)p);
    if( !ae_matrix_init(&p->xy, 0, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_matrix_init(&p->d, 0, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    return ae_true;
}


ae_bool _clusterizerstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic)
{
    clusterizerstate *dst = (clusterizerstate*)_dst;
    clusterizerstate *src = (clusterizerstate*)_src;
    dst->npoints = src->npoints;
    dst->nfeatures = src->nfeatures;
    dst->disttype = src->disttype;
    if( !ae_matrix_init_copy(&dst->xy, &src->xy, _state, make_automatic) )
        return ae_false;
    if( !ae_matrix_init_copy(&dst->d, &src->d, _state, make_automatic) )
        return ae_false;
    dst->ahcalgo = src->ahcalgo;
    dst->kmeansrestarts = src->kmeansrestarts;
    dst->kmeansmaxits = src->kmeansmaxits;
    return ae_true;
}


void _clusterizerstate_clear(void* _p)
{
    clusterizerstate *p = (clusterizerstate*)_p;
    ae_touch_ptr((void*)p);
    ae_matrix_clear(&p->xy);
    ae_matrix_clear(&p->d);
}


void _clusterizerstate_destroy(void* _p)
{
    clusterizerstate *p = (clusterizerstate*)_p;
    ae_touch_ptr((void*)p);
    ae_matrix_destroy(&p->xy);
    ae_matrix_destroy(&p->d);
}


ae_bool _ahcreport_init(void* _p, ae_state *_state, ae_bool make_automatic)
{
    ahcreport *p = (ahcreport*)_p;
    ae_touch_ptr((void*)p);
    if( !ae_vector_init(&p->p, 0, DT_INT, _state, make_automatic) )
        return ae_false;
    if( !ae_matrix_init(&p->z, 0, 0, DT_INT, _state, make_automatic) )
        return ae_false;
    if( !ae_matrix_init(&p->pz, 0, 0, DT_INT, _state, make_automatic) )
        return ae_false;
    if( !ae_matrix_init(&p->pm, 0, 0, DT_INT, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->mergedist, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    return ae_true;
}


ae_bool _ahcreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic)
{
    ahcreport *dst = (ahcreport*)_dst;
    ahcreport *src = (ahcreport*)_src;
    dst->npoints = src->npoints;
    if( !ae_vector_init_copy(&dst->p, &src->p, _state, make_automatic) )
        return ae_false;
    if( !ae_matrix_init_copy(&dst->z, &src->z, _state, make_automatic) )
        return ae_false;
    if( !ae_matrix_init_copy(&dst->pz, &src->pz, _state, make_automatic) )
        return ae_false;
    if( !ae_matrix_init_copy(&dst->pm, &src->pm, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->mergedist, &src->mergedist, _state, make_automatic) )
        return ae_false;
    return ae_true;
}


void _ahcreport_clear(void* _p)
{
    ahcreport *p = (ahcreport*)_p;
    ae_touch_ptr((void*)p);
    ae_vector_clear(&p->p);
    ae_matrix_clear(&p->z);
    ae_matrix_clear(&p->pz);
    ae_matrix_clear(&p->pm);
    ae_vector_clear(&p->mergedist);
}


void _ahcreport_destroy(void* _p)
{
    ahcreport *p = (ahcreport*)_p;
    ae_touch_ptr((void*)p);
    ae_vector_destroy(&p->p);
    ae_matrix_destroy(&p->z);
    ae_matrix_destroy(&p->pz);
    ae_matrix_destroy(&p->pm);
    ae_vector_destroy(&p->mergedist);
}


ae_bool _kmeansreport_init(void* _p, ae_state *_state, ae_bool make_automatic)
{
    kmeansreport *p = (kmeansreport*)_p;
    ae_touch_ptr((void*)p);
    if( !ae_matrix_init(&p->c, 0, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->cidx, 0, DT_INT, _state, make_automatic) )
        return ae_false;
    return ae_true;
}


ae_bool _kmeansreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic)
{
    kmeansreport *dst = (kmeansreport*)_dst;
    kmeansreport *src = (kmeansreport*)_src;
    dst->npoints = src->npoints;
    dst->nfeatures = src->nfeatures;
    dst->terminationtype = src->terminationtype;
    dst->k = src->k;
    if( !ae_matrix_init_copy(&dst->c, &src->c, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->cidx, &src->cidx, _state, make_automatic) )
        return ae_false;
    return ae_true;
}


void _kmeansreport_clear(void* _p)
{
    kmeansreport *p = (kmeansreport*)_p;
    ae_touch_ptr((void*)p);
    ae_matrix_clear(&p->c);
    ae_vector_clear(&p->cidx);
}


void _kmeansreport_destroy(void* _p)
{
    kmeansreport *p = (kmeansreport*)_p;
    ae_touch_ptr((void*)p);
    ae_matrix_destroy(&p->c);
    ae_vector_destroy(&p->cidx);
}




/*************************************************************************
k-means++ clusterization.
Backward compatibility function, we recommend to use CLUSTERING subpackage
as better replacement.

  -- ALGLIB --
     Copyright 21.03.2009 by Bochkanov Sergey
*************************************************************************/
void kmeansgenerate(/* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_int_t nvars,
     ae_int_t k,
     ae_int_t restarts,
     ae_int_t* info,
     /* Real    */ ae_matrix* c,
     /* Integer */ ae_vector* xyc,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_matrix dummy;

    ae_frame_make(_state, &_frame_block);
    *info = 0;
    ae_matrix_clear(c);
    ae_vector_clear(xyc);
    ae_matrix_init(&dummy, 0, 0, DT_REAL, _state, ae_true);

    kmeansgenerateinternal(xy, npoints, nvars, k, 0, restarts, info, c, ae_true, &dummy, ae_false, xyc, _state);
    ae_frame_leave(_state);
}




/*************************************************************************
This subroutine builds random decision forest.

INPUT PARAMETERS:
    XY          -   training set
    NPoints     -   training set size, NPoints>=1
    NVars       -   number of independent variables, NVars>=1
    NClasses    -   task type:
                    * NClasses=1 - regression task with one
                                   dependent variable
                    * NClasses>1 - classification task with
                                   NClasses classes.
    NTrees      -   number of trees in a forest, NTrees>=1.
                    recommended values: 50-100.
    R           -   percent of a training set used to build
                    individual trees. 0<R<=1.
                    recommended values: 0.1 <= R <= 0.66.

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<1, NVars<1, NClasses<1, NTrees<1, R<=0
                          or R>1).
                    *  1, if task has been solved
    DF          -   model built
    Rep         -   training report, contains error on a training set
                    and out-of-bag estimates of generalization error.

  -- ALGLIB --
     Copyright 19.02.2009 by Bochkanov Sergey
*************************************************************************/
void dfbuildrandomdecisionforest(/* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_int_t nvars,
     ae_int_t nclasses,
     ae_int_t ntrees,
     double r,
     ae_int_t* info,
     decisionforest* df,
     dfreport* rep,
     ae_state *_state)
{
    ae_int_t samplesize;

    *info = 0;
    _decisionforest_clear(df);
    _dfreport_clear(rep);

    if( ae_fp_less_eq(r,0)||ae_fp_greater(r,1) )
    {
        *info = -1;
        return;
    }
    samplesize = ae_maxint(ae_round(r*npoints, _state), 1, _state);
    dfbuildinternal(xy, npoints, nvars, nclasses, ntrees, samplesize, ae_maxint(nvars/2, 1, _state), dforest_dfusestrongsplits+dforest_dfuseevs, info, df, rep, _state);
}


/*************************************************************************
This subroutine builds random decision forest.
This function gives ability to tune number of variables used when choosing
best split.

INPUT PARAMETERS:
    XY          -   training set
    NPoints     -   training set size, NPoints>=1
    NVars       -   number of independent variables, NVars>=1
    NClasses    -   task type:
                    * NClasses=1 - regression task with one
                                   dependent variable
                    * NClasses>1 - classification task with
                                   NClasses classes.
    NTrees      -   number of trees in a forest, NTrees>=1.
                    recommended values: 50-100.
    NRndVars    -   number of variables used when choosing best split
    R           -   percent of a training set used to build
                    individual trees. 0<R<=1.
                    recommended values: 0.1 <= R <= 0.66.

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<1, NVars<1, NClasses<1, NTrees<1, R<=0
                          or R>1).
                    *  1, if task has been solved
    DF          -   model built
    Rep         -   training report, contains error on a training set
                    and out-of-bag estimates of generalization error.

  -- ALGLIB --
     Copyright 19.02.2009 by Bochkanov Sergey
*************************************************************************/
void dfbuildrandomdecisionforestx1(/* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_int_t nvars,
     ae_int_t nclasses,
     ae_int_t ntrees,
     ae_int_t nrndvars,
     double r,
     ae_int_t* info,
     decisionforest* df,
     dfreport* rep,
     ae_state *_state)
{
    ae_int_t samplesize;

    *info = 0;
    _decisionforest_clear(df);
    _dfreport_clear(rep);

    if( ae_fp_less_eq(r,0)||ae_fp_greater(r,1) )
    {
        *info = -1;
        return;
    }
    if( nrndvars<=0||nrndvars>nvars )
    {
        *info = -1;
        return;
    }
    samplesize = ae_maxint(ae_round(r*npoints, _state), 1, _state);
    dfbuildinternal(xy, npoints, nvars, nclasses, ntrees, samplesize, nrndvars, dforest_dfusestrongsplits+dforest_dfuseevs, info, df, rep, _state);
}


void dfbuildinternal(/* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_int_t nvars,
     ae_int_t nclasses,
     ae_int_t ntrees,
     ae_int_t samplesize,
     ae_int_t nfeatures,
     ae_int_t flags,
     ae_int_t* info,
     decisionforest* df,
     dfreport* rep,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t i;
    ae_int_t j;
    ae_int_t k;
    ae_int_t tmpi;
    ae_int_t lasttreeoffs;
    ae_int_t offs;
    ae_int_t ooboffs;
    ae_int_t treesize;
    ae_int_t nvarsinpool;
    ae_bool useevs;
    dfinternalbuffers bufs;
    ae_vector permbuf;
    ae_vector oobbuf;
    ae_vector oobcntbuf;
    ae_matrix xys;
    ae_vector x;
    ae_vector y;
    ae_int_t oobcnt;
    ae_int_t oobrelcnt;
    double v;
    double vmin;
    double vmax;
    ae_bool bflag;

    ae_frame_make(_state, &_frame_block);
    *info = 0;
    _decisionforest_clear(df);
    _dfreport_clear(rep);
    _dfinternalbuffers_init(&bufs, _state, ae_true);
    ae_vector_init(&permbuf, 0, DT_INT, _state, ae_true);
    ae_vector_init(&oobbuf, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&oobcntbuf, 0, DT_INT, _state, ae_true);
    ae_matrix_init(&xys, 0, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&x, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&y, 0, DT_REAL, _state, ae_true);

    
    /*
     * Test for inputs
     */
    if( (((((npoints<1||samplesize<1)||samplesize>npoints)||nvars<1)||nclasses<1)||ntrees<1)||nfeatures<1 )
    {
        *info = -1;
        ae_frame_leave(_state);
        return;
    }
    if( nclasses>1 )
    {
        for(i=0; i<=npoints-1; i++)
        {
            if( ae_round(xy->ptr.pp_double[i][nvars], _state)<0||ae_round(xy->ptr.pp_double[i][nvars], _state)>=nclasses )
            {
                *info = -2;
                ae_frame_leave(_state);
                return;
            }
        }
    }
    *info = 1;
    
    /*
     * Flags
     */
    useevs = flags/dforest_dfuseevs%2!=0;
    
    /*
     * Allocate data, prepare header
     */
    treesize = 1+dforest_innernodewidth*(samplesize-1)+dforest_leafnodewidth*samplesize;
    ae_vector_set_length(&permbuf, npoints-1+1, _state);
    ae_vector_set_length(&bufs.treebuf, treesize-1+1, _state);
    ae_vector_set_length(&bufs.idxbuf, npoints-1+1, _state);
    ae_vector_set_length(&bufs.tmpbufr, npoints-1+1, _state);
    ae_vector_set_length(&bufs.tmpbufr2, npoints-1+1, _state);
    ae_vector_set_length(&bufs.tmpbufi, npoints-1+1, _state);
    ae_vector_set_length(&bufs.sortrbuf, npoints, _state);
    ae_vector_set_length(&bufs.sortrbuf2, npoints, _state);
    ae_vector_set_length(&bufs.sortibuf, npoints, _state);
    ae_vector_set_length(&bufs.varpool, nvars-1+1, _state);
    ae_vector_set_length(&bufs.evsbin, nvars-1+1, _state);
    ae_vector_set_length(&bufs.evssplits, nvars-1+1, _state);
    ae_vector_set_length(&bufs.classibuf, 2*nclasses-1+1, _state);
    ae_vector_set_length(&oobbuf, nclasses*npoints-1+1, _state);
    ae_vector_set_length(&oobcntbuf, npoints-1+1, _state);
    ae_vector_set_length(&df->trees, ntrees*treesize-1+1, _state);
    ae_matrix_set_length(&xys, samplesize-1+1, nvars+1, _state);
    ae_vector_set_length(&x, nvars-1+1, _state);
    ae_vector_set_length(&y, nclasses-1+1, _state);
    for(i=0; i<=npoints-1; i++)
    {
        permbuf.ptr.p_int[i] = i;
    }
    for(i=0; i<=npoints*nclasses-1; i++)
    {
        oobbuf.ptr.p_double[i] = 0;
    }
    for(i=0; i<=npoints-1; i++)
    {
        oobcntbuf.ptr.p_int[i] = 0;
    }
    
    /*
     * Prepare variable pool and EVS (extended variable selection/splitting) buffers
     * (whether EVS is turned on or not):
     * 1. detect binary variables and pre-calculate splits for them
     * 2. detect variables with non-distinct values and exclude them from pool
     */
    for(i=0; i<=nvars-1; i++)
    {
        bufs.varpool.ptr.p_int[i] = i;
    }
    nvarsinpool = nvars;
    if( useevs )
    {
        for(j=0; j<=nvars-1; j++)
        {
            vmin = xy->ptr.pp_double[0][j];
            vmax = vmin;
            for(i=0; i<=npoints-1; i++)
            {
                v = xy->ptr.pp_double[i][j];
                vmin = ae_minreal(vmin, v, _state);
                vmax = ae_maxreal(vmax, v, _state);
            }
            if( ae_fp_eq(vmin,vmax) )
            {
                
                /*
                 * exclude variable from pool
                 */
                bufs.varpool.ptr.p_int[j] = bufs.varpool.ptr.p_int[nvarsinpool-1];
                bufs.varpool.ptr.p_int[nvarsinpool-1] = -1;
                nvarsinpool = nvarsinpool-1;
                continue;
            }
            bflag = ae_false;
            for(i=0; i<=npoints-1; i++)
            {
                v = xy->ptr.pp_double[i][j];
                if( ae_fp_neq(v,vmin)&&ae_fp_neq(v,vmax) )
                {
                    bflag = ae_true;
                    break;
                }
            }
            if( bflag )
            {
                
                /*
                 * non-binary variable
                 */
                bufs.evsbin.ptr.p_bool[j] = ae_false;
            }
            else
            {
                
                /*
                 * Prepare
                 */
                bufs.evsbin.ptr.p_bool[j] = ae_true;
                bufs.evssplits.ptr.p_double[j] = 0.5*(vmin+vmax);
                if( ae_fp_less_eq(bufs.evssplits.ptr.p_double[j],vmin) )
                {
                    bufs.evssplits.ptr.p_double[j] = vmax;
                }
            }
        }
    }
    
    /*
     * RANDOM FOREST FORMAT
     * W[0]         -   size of array
     * W[1]         -   version number
     * W[2]         -   NVars
     * W[3]         -   NClasses (1 for regression)
     * W[4]         -   NTrees
     * W[5]         -   trees offset
     *
     *
     * TREE FORMAT
     * W[Offs]      -   size of sub-array
     *     node info:
     * W[K+0]       -   variable number        (-1 for leaf mode)
     * W[K+1]       -   threshold              (class/value for leaf node)
     * W[K+2]       -   ">=" branch index      (absent for leaf node)
     *
     */
    df->nvars = nvars;
    df->nclasses = nclasses;
    df->ntrees = ntrees;
    
    /*
     * Build forest
     */
    offs = 0;
    for(i=0; i<=ntrees-1; i++)
    {
        
        /*
         * Prepare sample
         */
        for(k=0; k<=samplesize-1; k++)
        {
            j = k+ae_randominteger(npoints-k, _state);
            tmpi = permbuf.ptr.p_int[k];
            permbuf.ptr.p_int[k] = permbuf.ptr.p_int[j];
            permbuf.ptr.p_int[j] = tmpi;
            j = permbuf.ptr.p_int[k];
            ae_v_move(&xys.ptr.pp_double[k][0], 1, &xy->ptr.pp_double[j][0], 1, ae_v_len(0,nvars));
        }
        
        /*
         * build tree, copy
         */
        dforest_dfbuildtree(&xys, samplesize, nvars, nclasses, nfeatures, nvarsinpool, flags, &bufs, _state);
        j = ae_round(bufs.treebuf.ptr.p_double[0], _state);
        ae_v_move(&df->trees.ptr.p_double[offs], 1, &bufs.treebuf.ptr.p_double[0], 1, ae_v_len(offs,offs+j-1));
        lasttreeoffs = offs;
        offs = offs+j;
        
        /*
         * OOB estimates
         */
        for(k=samplesize; k<=npoints-1; k++)
        {
            for(j=0; j<=nclasses-1; j++)
            {
                y.ptr.p_double[j] = 0;
            }
            j = permbuf.ptr.p_int[k];
            ae_v_move(&x.ptr.p_double[0], 1, &xy->ptr.pp_double[j][0], 1, ae_v_len(0,nvars-1));
            dforest_dfprocessinternal(df, lasttreeoffs, &x, &y, _state);
            ae_v_add(&oobbuf.ptr.p_double[j*nclasses], 1, &y.ptr.p_double[0], 1, ae_v_len(j*nclasses,(j+1)*nclasses-1));
            oobcntbuf.ptr.p_int[j] = oobcntbuf.ptr.p_int[j]+1;
        }
    }
    df->bufsize = offs;
    
    /*
     * Normalize OOB results
     */
    for(i=0; i<=npoints-1; i++)
    {
        if( oobcntbuf.ptr.p_int[i]!=0 )
        {
            v = (double)1/(double)oobcntbuf.ptr.p_int[i];
            ae_v_muld(&oobbuf.ptr.p_double[i*nclasses], 1, ae_v_len(i*nclasses,i*nclasses+nclasses-1), v);
        }
    }
    
    /*
     * Calculate training set estimates
     */
    rep->relclserror = dfrelclserror(df, xy, npoints, _state);
    rep->avgce = dfavgce(df, xy, npoints, _state);
    rep->rmserror = dfrmserror(df, xy, npoints, _state);
    rep->avgerror = dfavgerror(df, xy, npoints, _state);
    rep->avgrelerror = dfavgrelerror(df, xy, npoints, _state);
    
    /*
     * Calculate OOB estimates.
     */
    rep->oobrelclserror = 0;
    rep->oobavgce = 0;
    rep->oobrmserror = 0;
    rep->oobavgerror = 0;
    rep->oobavgrelerror = 0;
    oobcnt = 0;
    oobrelcnt = 0;
    for(i=0; i<=npoints-1; i++)
    {
        if( oobcntbuf.ptr.p_int[i]!=0 )
        {
            ooboffs = i*nclasses;
            if( nclasses>1 )
            {
                
                /*
                 * classification-specific code
                 */
                k = ae_round(xy->ptr.pp_double[i][nvars], _state);
                tmpi = 0;
                for(j=1; j<=nclasses-1; j++)
                {
                    if( ae_fp_greater(oobbuf.ptr.p_double[ooboffs+j],oobbuf.ptr.p_double[ooboffs+tmpi]) )
                    {
                        tmpi = j;
                    }
                }
                if( tmpi!=k )
                {
                    rep->oobrelclserror = rep->oobrelclserror+1;
                }
                if( ae_fp_neq(oobbuf.ptr.p_double[ooboffs+k],0) )
                {
                    rep->oobavgce = rep->oobavgce-ae_log(oobbuf.ptr.p_double[ooboffs+k], _state);
                }
                else
                {
                    rep->oobavgce = rep->oobavgce-ae_log(ae_minrealnumber, _state);
                }
                for(j=0; j<=nclasses-1; j++)
                {
                    if( j==k )
                    {
                        rep->oobrmserror = rep->oobrmserror+ae_sqr(oobbuf.ptr.p_double[ooboffs+j]-1, _state);
                        rep->oobavgerror = rep->oobavgerror+ae_fabs(oobbuf.ptr.p_double[ooboffs+j]-1, _state);
                        rep->oobavgrelerror = rep->oobavgrelerror+ae_fabs(oobbuf.ptr.p_double[ooboffs+j]-1, _state);
                        oobrelcnt = oobrelcnt+1;
                    }
                    else
                    {
                        rep->oobrmserror = rep->oobrmserror+ae_sqr(oobbuf.ptr.p_double[ooboffs+j], _state);
                        rep->oobavgerror = rep->oobavgerror+ae_fabs(oobbuf.ptr.p_double[ooboffs+j], _state);
                    }
                }
            }
            else
            {
                
                /*
                 * regression-specific code
                 */
                rep->oobrmserror = rep->oobrmserror+ae_sqr(oobbuf.ptr.p_double[ooboffs]-xy->ptr.pp_double[i][nvars], _state);
                rep->oobavgerror = rep->oobavgerror+ae_fabs(oobbuf.ptr.p_double[ooboffs]-xy->ptr.pp_double[i][nvars], _state);
                if( ae_fp_neq(xy->ptr.pp_double[i][nvars],0) )
                {
                    rep->oobavgrelerror = rep->oobavgrelerror+ae_fabs((oobbuf.ptr.p_double[ooboffs]-xy->ptr.pp_double[i][nvars])/xy->ptr.pp_double[i][nvars], _state);
                    oobrelcnt = oobrelcnt+1;
                }
            }
            
            /*
             * update OOB estimates count.
             */
            oobcnt = oobcnt+1;
        }
    }
    if( oobcnt>0 )
    {
        rep->oobrelclserror = rep->oobrelclserror/oobcnt;
        rep->oobavgce = rep->oobavgce/oobcnt;
        rep->oobrmserror = ae_sqrt(rep->oobrmserror/(oobcnt*nclasses), _state);
        rep->oobavgerror = rep->oobavgerror/(oobcnt*nclasses);
        if( oobrelcnt>0 )
        {
            rep->oobavgrelerror = rep->oobavgrelerror/oobrelcnt;
        }
    }
    ae_frame_leave(_state);
}


/*************************************************************************
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.

  -- ALGLIB --
     Copyright 16.02.2009 by Bochkanov Sergey
*************************************************************************/
void dfprocess(decisionforest* df,
     /* Real    */ ae_vector* x,
     /* Real    */ ae_vector* y,
     ae_state *_state)
{
    ae_int_t offs;
    ae_int_t i;
    double v;


    
    /*
     * Proceed
     */
    if( y->cnt<df->nclasses )
    {
        ae_vector_set_length(y, df->nclasses, _state);
    }
    offs = 0;
    for(i=0; i<=df->nclasses-1; i++)
    {
        y->ptr.p_double[i] = 0;
    }
    for(i=0; i<=df->ntrees-1; i++)
    {
        
        /*
         * Process basic tree
         */
        dforest_dfprocessinternal(df, offs, x, y, _state);
        
        /*
         * Next tree
         */
        offs = offs+ae_round(df->trees.ptr.p_double[offs], _state);
    }
    v = (double)1/(double)df->ntrees;
    ae_v_muld(&y->ptr.p_double[0], 1, ae_v_len(0,df->nclasses-1), v);
}


/*************************************************************************
'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.

  -- ALGLIB --
     Copyright 28.02.2010 by Bochkanov Sergey
*************************************************************************/
void dfprocessi(decisionforest* df,
     /* Real    */ ae_vector* x,
     /* Real    */ ae_vector* y,
     ae_state *_state)
{

    ae_vector_clear(y);

    dfprocess(df, x, y, _state);
}


/*************************************************************************
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.

  -- ALGLIB --
     Copyright 16.02.2009 by Bochkanov Sergey
*************************************************************************/
double dfrelclserror(decisionforest* df,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_state *_state)
{
    double result;


    result = (double)dforest_dfclserror(df, xy, npoints, _state)/(double)npoints;
    return result;
}


/*************************************************************************
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.

  -- ALGLIB --
     Copyright 16.02.2009 by Bochkanov Sergey
*************************************************************************/
double dfavgce(decisionforest* df,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_vector x;
    ae_vector y;
    ae_int_t i;
    ae_int_t j;
    ae_int_t k;
    ae_int_t tmpi;
    double result;

    ae_frame_make(_state, &_frame_block);
    ae_vector_init(&x, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&y, 0, DT_REAL, _state, ae_true);

    ae_vector_set_length(&x, df->nvars-1+1, _state);
    ae_vector_set_length(&y, df->nclasses-1+1, _state);
    result = 0;
    for(i=0; i<=npoints-1; i++)
    {
        ae_v_move(&x.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,df->nvars-1));
        dfprocess(df, &x, &y, _state);
        if( df->nclasses>1 )
        {
            
            /*
             * classification-specific code
             */
            k = ae_round(xy->ptr.pp_double[i][df->nvars], _state);
            tmpi = 0;
            for(j=1; j<=df->nclasses-1; j++)
            {
                if( ae_fp_greater(y.ptr.p_double[j],y.ptr.p_double[tmpi]) )
                {
                    tmpi = j;
                }
            }
            if( ae_fp_neq(y.ptr.p_double[k],0) )
            {
                result = result-ae_log(y.ptr.p_double[k], _state);
            }
            else
            {
                result = result-ae_log(ae_minrealnumber, _state);
            }
        }
    }
    result = result/npoints;
    ae_frame_leave(_state);
    return result;
}


/*************************************************************************
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.

  -- ALGLIB --
     Copyright 16.02.2009 by Bochkanov Sergey
*************************************************************************/
double dfrmserror(decisionforest* df,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_vector x;
    ae_vector y;
    ae_int_t i;
    ae_int_t j;
    ae_int_t k;
    ae_int_t tmpi;
    double result;

    ae_frame_make(_state, &_frame_block);
    ae_vector_init(&x, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&y, 0, DT_REAL, _state, ae_true);

    ae_vector_set_length(&x, df->nvars-1+1, _state);
    ae_vector_set_length(&y, df->nclasses-1+1, _state);
    result = 0;
    for(i=0; i<=npoints-1; i++)
    {
        ae_v_move(&x.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,df->nvars-1));
        dfprocess(df, &x, &y, _state);
        if( df->nclasses>1 )
        {
            
            /*
             * classification-specific code
             */
            k = ae_round(xy->ptr.pp_double[i][df->nvars], _state);
            tmpi = 0;
            for(j=1; j<=df->nclasses-1; j++)
            {
                if( ae_fp_greater(y.ptr.p_double[j],y.ptr.p_double[tmpi]) )
                {
                    tmpi = j;
                }
            }
            for(j=0; j<=df->nclasses-1; j++)
            {
                if( j==k )
                {
                    result = result+ae_sqr(y.ptr.p_double[j]-1, _state);
                }
                else
                {
                    result = result+ae_sqr(y.ptr.p_double[j], _state);
                }
            }
        }
        else
        {
            
            /*
             * regression-specific code
             */
            result = result+ae_sqr(y.ptr.p_double[0]-xy->ptr.pp_double[i][df->nvars], _state);
        }
    }
    result = ae_sqrt(result/(npoints*df->nclasses), _state);
    ae_frame_leave(_state);
    return result;
}


/*************************************************************************
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.

  -- ALGLIB --
     Copyright 16.02.2009 by Bochkanov Sergey
*************************************************************************/
double dfavgerror(decisionforest* df,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_vector x;
    ae_vector y;
    ae_int_t i;
    ae_int_t j;
    ae_int_t k;
    double result;

    ae_frame_make(_state, &_frame_block);
    ae_vector_init(&x, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&y, 0, DT_REAL, _state, ae_true);

    ae_vector_set_length(&x, df->nvars-1+1, _state);
    ae_vector_set_length(&y, df->nclasses-1+1, _state);
    result = 0;
    for(i=0; i<=npoints-1; i++)
    {
        ae_v_move(&x.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,df->nvars-1));
        dfprocess(df, &x, &y, _state);
        if( df->nclasses>1 )
        {
            
            /*
             * classification-specific code
             */
            k = ae_round(xy->ptr.pp_double[i][df->nvars], _state);
            for(j=0; j<=df->nclasses-1; j++)
            {
                if( j==k )
                {
                    result = result+ae_fabs(y.ptr.p_double[j]-1, _state);
                }
                else
                {
                    result = result+ae_fabs(y.ptr.p_double[j], _state);
                }
            }
        }
        else
        {
            
            /*
             * regression-specific code
             */
            result = result+ae_fabs(y.ptr.p_double[0]-xy->ptr.pp_double[i][df->nvars], _state);
        }
    }
    result = result/(npoints*df->nclasses);
    ae_frame_leave(_state);
    return result;
}


/*************************************************************************
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.

  -- ALGLIB --
     Copyright 16.02.2009 by Bochkanov Sergey
*************************************************************************/
double dfavgrelerror(decisionforest* df,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_vector x;
    ae_vector y;
    ae_int_t relcnt;
    ae_int_t i;
    ae_int_t j;
    ae_int_t k;
    double result;

    ae_frame_make(_state, &_frame_block);
    ae_vector_init(&x, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&y, 0, DT_REAL, _state, ae_true);

    ae_vector_set_length(&x, df->nvars-1+1, _state);
    ae_vector_set_length(&y, df->nclasses-1+1, _state);
    result = 0;
    relcnt = 0;
    for(i=0; i<=npoints-1; i++)
    {
        ae_v_move(&x.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,df->nvars-1));
        dfprocess(df, &x, &y, _state);
        if( df->nclasses>1 )
        {
            
            /*
             * classification-specific code
             */
            k = ae_round(xy->ptr.pp_double[i][df->nvars], _state);
            for(j=0; j<=df->nclasses-1; j++)
            {
                if( j==k )
                {
                    result = result+ae_fabs(y.ptr.p_double[j]-1, _state);
                    relcnt = relcnt+1;
                }
            }
        }
        else
        {
            
            /*
             * regression-specific code
             */
            if( ae_fp_neq(xy->ptr.pp_double[i][df->nvars],0) )
            {
                result = result+ae_fabs((y.ptr.p_double[0]-xy->ptr.pp_double[i][df->nvars])/xy->ptr.pp_double[i][df->nvars], _state);
                relcnt = relcnt+1;
            }
        }
    }
    if( relcnt>0 )
    {
        result = result/relcnt;
    }
    ae_frame_leave(_state);
    return result;
}


/*************************************************************************
Copying of DecisionForest strucure

INPUT PARAMETERS:
    DF1 -   original

OUTPUT PARAMETERS:
    DF2 -   copy

  -- ALGLIB --
     Copyright 13.02.2009 by Bochkanov Sergey
*************************************************************************/
void dfcopy(decisionforest* df1, decisionforest* df2, ae_state *_state)
{

    _decisionforest_clear(df2);

    df2->nvars = df1->nvars;
    df2->nclasses = df1->nclasses;
    df2->ntrees = df1->ntrees;
    df2->bufsize = df1->bufsize;
    ae_vector_set_length(&df2->trees, df1->bufsize-1+1, _state);
    ae_v_move(&df2->trees.ptr.p_double[0], 1, &df1->trees.ptr.p_double[0], 1, ae_v_len(0,df1->bufsize-1));
}


/*************************************************************************
Serializer: allocation

  -- ALGLIB --
     Copyright 14.03.2011 by Bochkanov Sergey
*************************************************************************/
void dfalloc(ae_serializer* s, decisionforest* forest, ae_state *_state)
{


    ae_serializer_alloc_entry(s);
    ae_serializer_alloc_entry(s);
    ae_serializer_alloc_entry(s);
    ae_serializer_alloc_entry(s);
    ae_serializer_alloc_entry(s);
    ae_serializer_alloc_entry(s);
    allocrealarray(s, &forest->trees, forest->bufsize, _state);
}


/*************************************************************************
Serializer: serialization

  -- ALGLIB --
     Copyright 14.03.2011 by Bochkanov Sergey
*************************************************************************/
void dfserialize(ae_serializer* s,
     decisionforest* forest,
     ae_state *_state)
{


    ae_serializer_serialize_int(s, getrdfserializationcode(_state), _state);
    ae_serializer_serialize_int(s, dforest_dffirstversion, _state);
    ae_serializer_serialize_int(s, forest->nvars, _state);
    ae_serializer_serialize_int(s, forest->nclasses, _state);
    ae_serializer_serialize_int(s, forest->ntrees, _state);
    ae_serializer_serialize_int(s, forest->bufsize, _state);
    serializerealarray(s, &forest->trees, forest->bufsize, _state);
}


/*************************************************************************
Serializer: unserialization

  -- ALGLIB --
     Copyright 14.03.2011 by Bochkanov Sergey
*************************************************************************/
void dfunserialize(ae_serializer* s,
     decisionforest* forest,
     ae_state *_state)
{
    ae_int_t i0;
    ae_int_t i1;

    _decisionforest_clear(forest);

    
    /*
     * check correctness of header
     */
    ae_serializer_unserialize_int(s, &i0, _state);
    ae_assert(i0==getrdfserializationcode(_state), "DFUnserialize: stream header corrupted", _state);
    ae_serializer_unserialize_int(s, &i1, _state);
    ae_assert(i1==dforest_dffirstversion, "DFUnserialize: stream header corrupted", _state);
    
    /*
     * Unserialize data
     */
    ae_serializer_unserialize_int(s, &forest->nvars, _state);
    ae_serializer_unserialize_int(s, &forest->nclasses, _state);
    ae_serializer_unserialize_int(s, &forest->ntrees, _state);
    ae_serializer_unserialize_int(s, &forest->bufsize, _state);
    unserializerealarray(s, &forest->trees, _state);
}


/*************************************************************************
Classification error
*************************************************************************/
static ae_int_t dforest_dfclserror(decisionforest* df,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_vector x;
    ae_vector y;
    ae_int_t i;
    ae_int_t j;
    ae_int_t k;
    ae_int_t tmpi;
    ae_int_t result;

    ae_frame_make(_state, &_frame_block);
    ae_vector_init(&x, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&y, 0, DT_REAL, _state, ae_true);

    if( df->nclasses<=1 )
    {
        result = 0;
        ae_frame_leave(_state);
        return result;
    }
    ae_vector_set_length(&x, df->nvars-1+1, _state);
    ae_vector_set_length(&y, df->nclasses-1+1, _state);
    result = 0;
    for(i=0; i<=npoints-1; i++)
    {
        ae_v_move(&x.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,df->nvars-1));
        dfprocess(df, &x, &y, _state);
        k = ae_round(xy->ptr.pp_double[i][df->nvars], _state);
        tmpi = 0;
        for(j=1; j<=df->nclasses-1; j++)
        {
            if( ae_fp_greater(y.ptr.p_double[j],y.ptr.p_double[tmpi]) )
            {
                tmpi = j;
            }
        }
        if( tmpi!=k )
        {
            result = result+1;
        }
    }
    ae_frame_leave(_state);
    return result;
}


/*************************************************************************
Internal subroutine for processing one decision tree starting at Offs
*************************************************************************/
static void dforest_dfprocessinternal(decisionforest* df,
     ae_int_t offs,
     /* Real    */ ae_vector* x,
     /* Real    */ ae_vector* y,
     ae_state *_state)
{
    ae_int_t k;
    ae_int_t idx;


    
    /*
     * Set pointer to the root
     */
    k = offs+1;
    
    /*
     * Navigate through the tree
     */
    for(;;)
    {
        if( ae_fp_eq(df->trees.ptr.p_double[k],-1) )
        {
            if( df->nclasses==1 )
            {
                y->ptr.p_double[0] = y->ptr.p_double[0]+df->trees.ptr.p_double[k+1];
            }
            else
            {
                idx = ae_round(df->trees.ptr.p_double[k+1], _state);
                y->ptr.p_double[idx] = y->ptr.p_double[idx]+1;
            }
            break;
        }
        if( ae_fp_less(x->ptr.p_double[ae_round(df->trees.ptr.p_double[k], _state)],df->trees.ptr.p_double[k+1]) )
        {
            k = k+dforest_innernodewidth;
        }
        else
        {
            k = offs+ae_round(df->trees.ptr.p_double[k+2], _state);
        }
    }
}


/*************************************************************************
Builds one decision tree. Just a wrapper for the DFBuildTreeRec.
*************************************************************************/
static void dforest_dfbuildtree(/* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_int_t nvars,
     ae_int_t nclasses,
     ae_int_t nfeatures,
     ae_int_t nvarsinpool,
     ae_int_t flags,
     dfinternalbuffers* bufs,
     ae_state *_state)
{
    ae_int_t numprocessed;
    ae_int_t i;


    ae_assert(npoints>0, "Assertion failed", _state);
    
    /*
     * Prepare IdxBuf. It stores indices of the training set elements.
     * When training set is being split, contents of IdxBuf is
     * correspondingly reordered so we can know which elements belong
     * to which branch of decision tree.
     */
    for(i=0; i<=npoints-1; i++)
    {
        bufs->idxbuf.ptr.p_int[i] = i;
    }
    
    /*
     * Recursive procedure
     */
    numprocessed = 1;
    dforest_dfbuildtreerec(xy, npoints, nvars, nclasses, nfeatures, nvarsinpool, flags, &numprocessed, 0, npoints-1, bufs, _state);
    bufs->treebuf.ptr.p_double[0] = numprocessed;
}


/*************************************************************************
Builds one decision tree (internal recursive subroutine)

Parameters:
    TreeBuf     -   large enough array, at least TreeSize
    IdxBuf      -   at least NPoints elements
    TmpBufR     -   at least NPoints
    TmpBufR2    -   at least NPoints
    TmpBufI     -   at least NPoints
    TmpBufI2    -   at least NPoints+1
*************************************************************************/
static void dforest_dfbuildtreerec(/* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_int_t nvars,
     ae_int_t nclasses,
     ae_int_t nfeatures,
     ae_int_t nvarsinpool,
     ae_int_t flags,
     ae_int_t* numprocessed,
     ae_int_t idx1,
     ae_int_t idx2,
     dfinternalbuffers* bufs,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t j;
    ae_int_t k;
    ae_bool bflag;
    ae_int_t i1;
    ae_int_t i2;
    ae_int_t info;
    double sl;
    double sr;
    double w;
    ae_int_t idxbest;
    double ebest;
    double tbest;
    ae_int_t varcur;
    double s;
    double v;
    double v1;
    double v2;
    double threshold;
    ae_int_t oldnp;
    double currms;
    ae_bool useevs;


    
    /*
     * these initializers are not really necessary,
     * but without them compiler complains about uninitialized locals
     */
    tbest = 0;
    
    /*
     * Prepare
     */
    ae_assert(npoints>0, "Assertion failed", _state);
    ae_assert(idx2>=idx1, "Assertion failed", _state);
    useevs = flags/dforest_dfuseevs%2!=0;
    
    /*
     * Leaf node
     */
    if( idx2==idx1 )
    {
        bufs->treebuf.ptr.p_double[*numprocessed] = -1;
        bufs->treebuf.ptr.p_double[*numprocessed+1] = xy->ptr.pp_double[bufs->idxbuf.ptr.p_int[idx1]][nvars];
        *numprocessed = *numprocessed+dforest_leafnodewidth;
        return;
    }
    
    /*
     * Non-leaf node.
     * Select random variable, prepare split:
     * 1. prepare default solution - no splitting, class at random
     * 2. investigate possible splits, compare with default/best
     */
    idxbest = -1;
    if( nclasses>1 )
    {
        
        /*
         * default solution for classification
         */
        for(i=0; i<=nclasses-1; i++)
        {
            bufs->classibuf.ptr.p_int[i] = 0;
        }
        s = idx2-idx1+1;
        for(i=idx1; i<=idx2; i++)
        {
            j = ae_round(xy->ptr.pp_double[bufs->idxbuf.ptr.p_int[i]][nvars], _state);
            bufs->classibuf.ptr.p_int[j] = bufs->classibuf.ptr.p_int[j]+1;
        }
        ebest = 0;
        for(i=0; i<=nclasses-1; i++)
        {
            ebest = ebest+bufs->classibuf.ptr.p_int[i]*ae_sqr(1-bufs->classibuf.ptr.p_int[i]/s, _state)+(s-bufs->classibuf.ptr.p_int[i])*ae_sqr(bufs->classibuf.ptr.p_int[i]/s, _state);
        }
        ebest = ae_sqrt(ebest/(nclasses*(idx2-idx1+1)), _state);
    }
    else
    {
        
        /*
         * default solution for regression
         */
        v = 0;
        for(i=idx1; i<=idx2; i++)
        {
            v = v+xy->ptr.pp_double[bufs->idxbuf.ptr.p_int[i]][nvars];
        }
        v = v/(idx2-idx1+1);
        ebest = 0;
        for(i=idx1; i<=idx2; i++)
        {
            ebest = ebest+ae_sqr(xy->ptr.pp_double[bufs->idxbuf.ptr.p_int[i]][nvars]-v, _state);
        }
        ebest = ae_sqrt(ebest/(idx2-idx1+1), _state);
    }
    i = 0;
    while(i<=ae_minint(nfeatures, nvarsinpool, _state)-1)
    {
        
        /*
         * select variables from pool
         */
        j = i+ae_randominteger(nvarsinpool-i, _state);
        k = bufs->varpool.ptr.p_int[i];
        bufs->varpool.ptr.p_int[i] = bufs->varpool.ptr.p_int[j];
        bufs->varpool.ptr.p_int[j] = k;
        varcur = bufs->varpool.ptr.p_int[i];
        
        /*
         * load variable values to working array
         *
         * apply EVS preprocessing: if all variable values are same,
         * variable is excluded from pool.
         *
         * This is necessary for binary pre-splits (see later) to work.
         */
        for(j=idx1; j<=idx2; j++)
        {
            bufs->tmpbufr.ptr.p_double[j-idx1] = xy->ptr.pp_double[bufs->idxbuf.ptr.p_int[j]][varcur];
        }
        if( useevs )
        {
            bflag = ae_false;
            v = bufs->tmpbufr.ptr.p_double[0];
            for(j=0; j<=idx2-idx1; j++)
            {
                if( ae_fp_neq(bufs->tmpbufr.ptr.p_double[j],v) )
                {
                    bflag = ae_true;
                    break;
                }
            }
            if( !bflag )
            {
                
                /*
                 * exclude variable from pool,
                 * go to the next iteration.
                 * I is not increased.
                 */
                k = bufs->varpool.ptr.p_int[i];
                bufs->varpool.ptr.p_int[i] = bufs->varpool.ptr.p_int[nvarsinpool-1];
                bufs->varpool.ptr.p_int[nvarsinpool-1] = k;
                nvarsinpool = nvarsinpool-1;
                continue;
            }
        }
        
        /*
         * load labels to working array
         */
        if( nclasses>1 )
        {
            for(j=idx1; j<=idx2; j++)
            {
                bufs->tmpbufi.ptr.p_int[j-idx1] = ae_round(xy->ptr.pp_double[bufs->idxbuf.ptr.p_int[j]][nvars], _state);
            }
        }
        else
        {
            for(j=idx1; j<=idx2; j++)
            {
                bufs->tmpbufr2.ptr.p_double[j-idx1] = xy->ptr.pp_double[bufs->idxbuf.ptr.p_int[j]][nvars];
            }
        }
        
        /*
         * calculate split
         */
        if( useevs&&bufs->evsbin.ptr.p_bool[varcur] )
        {
            
            /*
             * Pre-calculated splits for binary variables.
             * Threshold is already known, just calculate RMS error
             */
            threshold = bufs->evssplits.ptr.p_double[varcur];
            if( nclasses>1 )
            {
                
                /*
                 * classification-specific code
                 */
                for(j=0; j<=2*nclasses-1; j++)
                {
                    bufs->classibuf.ptr.p_int[j] = 0;
                }
                sl = 0;
                sr = 0;
                for(j=0; j<=idx2-idx1; j++)
                {
                    k = bufs->tmpbufi.ptr.p_int[j];
                    if( ae_fp_less(bufs->tmpbufr.ptr.p_double[j],threshold) )
                    {
                        bufs->classibuf.ptr.p_int[k] = bufs->classibuf.ptr.p_int[k]+1;
                        sl = sl+1;
                    }
                    else
                    {
                        bufs->classibuf.ptr.p_int[k+nclasses] = bufs->classibuf.ptr.p_int[k+nclasses]+1;
                        sr = sr+1;
                    }
                }
                ae_assert(ae_fp_neq(sl,0)&&ae_fp_neq(sr,0), "DFBuildTreeRec: something strange!", _state);
                currms = 0;
                for(j=0; j<=nclasses-1; j++)
                {
                    w = bufs->classibuf.ptr.p_int[j];
                    currms = currms+w*ae_sqr(w/sl-1, _state);
                    currms = currms+(sl-w)*ae_sqr(w/sl, _state);
                    w = bufs->classibuf.ptr.p_int[nclasses+j];
                    currms = currms+w*ae_sqr(w/sr-1, _state);
                    currms = currms+(sr-w)*ae_sqr(w/sr, _state);
                }
                currms = ae_sqrt(currms/(nclasses*(idx2-idx1+1)), _state);
            }
            else
            {
                
                /*
                 * regression-specific code
                 */
                sl = 0;
                sr = 0;
                v1 = 0;
                v2 = 0;
                for(j=0; j<=idx2-idx1; j++)
                {
                    if( ae_fp_less(bufs->tmpbufr.ptr.p_double[j],threshold) )
                    {
                        v1 = v1+bufs->tmpbufr2.ptr.p_double[j];
                        sl = sl+1;
                    }
                    else
                    {
                        v2 = v2+bufs->tmpbufr2.ptr.p_double[j];
                        sr = sr+1;
                    }
                }
                ae_assert(ae_fp_neq(sl,0)&&ae_fp_neq(sr,0), "DFBuildTreeRec: something strange!", _state);
                v1 = v1/sl;
                v2 = v2/sr;
                currms = 0;
                for(j=0; j<=idx2-idx1; j++)
                {
                    if( ae_fp_less(bufs->tmpbufr.ptr.p_double[j],threshold) )
                    {
                        currms = currms+ae_sqr(v1-bufs->tmpbufr2.ptr.p_double[j], _state);
                    }
                    else
                    {
                        currms = currms+ae_sqr(v2-bufs->tmpbufr2.ptr.p_double[j], _state);
                    }
                }
                currms = ae_sqrt(currms/(idx2-idx1+1), _state);
            }
            info = 1;
        }
        else
        {
            
            /*
             * Generic splits
             */
            if( nclasses>1 )
            {
                dforest_dfsplitc(&bufs->tmpbufr, &bufs->tmpbufi, &bufs->classibuf, idx2-idx1+1, nclasses, dforest_dfusestrongsplits, &info, &threshold, &currms, &bufs->sortrbuf, &bufs->sortibuf, _state);
            }
            else
            {
                dforest_dfsplitr(&bufs->tmpbufr, &bufs->tmpbufr2, idx2-idx1+1, dforest_dfusestrongsplits, &info, &threshold, &currms, &bufs->sortrbuf, &bufs->sortrbuf2, _state);
            }
        }
        if( info>0 )
        {
            if( ae_fp_less_eq(currms,ebest) )
            {
                ebest = currms;
                idxbest = varcur;
                tbest = threshold;
            }
        }
        
        /*
         * Next iteration
         */
        i = i+1;
    }
    
    /*
     * to split or not to split
     */
    if( idxbest<0 )
    {
        
        /*
         * All values are same, cannot split.
         */
        bufs->treebuf.ptr.p_double[*numprocessed] = -1;
        if( nclasses>1 )
        {
            
            /*
             * Select random class label (randomness allows us to
             * approximate distribution of the classes)
             */
            bufs->treebuf.ptr.p_double[*numprocessed+1] = ae_round(xy->ptr.pp_double[bufs->idxbuf.ptr.p_int[idx1+ae_randominteger(idx2-idx1+1, _state)]][nvars], _state);
        }
        else
        {
            
            /*
             * Select average (for regression task).
             */
            v = 0;
            for(i=idx1; i<=idx2; i++)
            {
                v = v+xy->ptr.pp_double[bufs->idxbuf.ptr.p_int[i]][nvars]/(idx2-idx1+1);
            }
            bufs->treebuf.ptr.p_double[*numprocessed+1] = v;
        }
        *numprocessed = *numprocessed+dforest_leafnodewidth;
    }
    else
    {
        
        /*
         * we can split
         */
        bufs->treebuf.ptr.p_double[*numprocessed] = idxbest;
        bufs->treebuf.ptr.p_double[*numprocessed+1] = tbest;
        i1 = idx1;
        i2 = idx2;
        while(i1<=i2)
        {
            
            /*
             * Reorder indices so that left partition is in [Idx1..I1-1],
             * and right partition is in [I2+1..Idx2]
             */
            if( ae_fp_less(xy->ptr.pp_double[bufs->idxbuf.ptr.p_int[i1]][idxbest],tbest) )
            {
                i1 = i1+1;
                continue;
            }
            if( ae_fp_greater_eq(xy->ptr.pp_double[bufs->idxbuf.ptr.p_int[i2]][idxbest],tbest) )
            {
                i2 = i2-1;
                continue;
            }
            j = bufs->idxbuf.ptr.p_int[i1];
            bufs->idxbuf.ptr.p_int[i1] = bufs->idxbuf.ptr.p_int[i2];
            bufs->idxbuf.ptr.p_int[i2] = j;
            i1 = i1+1;
            i2 = i2-1;
        }
        oldnp = *numprocessed;
        *numprocessed = *numprocessed+dforest_innernodewidth;
        dforest_dfbuildtreerec(xy, npoints, nvars, nclasses, nfeatures, nvarsinpool, flags, numprocessed, idx1, i1-1, bufs, _state);
        bufs->treebuf.ptr.p_double[oldnp+2] = *numprocessed;
        dforest_dfbuildtreerec(xy, npoints, nvars, nclasses, nfeatures, nvarsinpool, flags, numprocessed, i2+1, idx2, bufs, _state);
    }
}


/*************************************************************************
Makes split on attribute
*************************************************************************/
static void dforest_dfsplitc(/* Real    */ ae_vector* x,
     /* Integer */ ae_vector* c,
     /* Integer */ ae_vector* cntbuf,
     ae_int_t n,
     ae_int_t nc,
     ae_int_t flags,
     ae_int_t* info,
     double* threshold,
     double* e,
     /* Real    */ ae_vector* sortrbuf,
     /* Integer */ ae_vector* sortibuf,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t neq;
    ae_int_t nless;
    ae_int_t ngreater;
    ae_int_t q;
    ae_int_t qmin;
    ae_int_t qmax;
    ae_int_t qcnt;
    double cursplit;
    ae_int_t nleft;
    double v;
    double cure;
    double w;
    double sl;
    double sr;

    *info = 0;
    *threshold = 0;
    *e = 0;

    tagsortfasti(x, c, sortrbuf, sortibuf, n, _state);
    *e = ae_maxrealnumber;
    *threshold = 0.5*(x->ptr.p_double[0]+x->ptr.p_double[n-1]);
    *info = -3;
    if( flags/dforest_dfusestrongsplits%2==0 )
    {
        
        /*
         * weak splits, split at half
         */
        qcnt = 2;
        qmin = 1;
        qmax = 1;
    }
    else
    {
        
        /*
         * strong splits: choose best quartile
         */
        qcnt = 4;
        qmin = 1;
        qmax = 3;
    }
    for(q=qmin; q<=qmax; q++)
    {
        cursplit = x->ptr.p_double[n*q/qcnt];
        neq = 0;
        nless = 0;
        ngreater = 0;
        for(i=0; i<=n-1; i++)
        {
            if( ae_fp_less(x->ptr.p_double[i],cursplit) )
            {
                nless = nless+1;
            }
            if( ae_fp_eq(x->ptr.p_double[i],cursplit) )
            {
                neq = neq+1;
            }
            if( ae_fp_greater(x->ptr.p_double[i],cursplit) )
            {
                ngreater = ngreater+1;
            }
        }
        ae_assert(neq!=0, "DFSplitR: NEq=0, something strange!!!", _state);
        if( nless!=0||ngreater!=0 )
        {
            
            /*
             * set threshold between two partitions, with
             * some tweaking to avoid problems with floating point
             * arithmetics.
             *
             * The problem is that when you calculates C = 0.5*(A+B) there
             * can be no C which lies strictly between A and B (for example,
             * there is no floating point number which is
             * greater than 1 and less than 1+eps). In such situations
             * we choose right side as theshold (remember that
             * points which lie on threshold falls to the right side).
             */
            if( nless<ngreater )
            {
                cursplit = 0.5*(x->ptr.p_double[nless+neq-1]+x->ptr.p_double[nless+neq]);
                nleft = nless+neq;
                if( ae_fp_less_eq(cursplit,x->ptr.p_double[nless+neq-1]) )
                {
                    cursplit = x->ptr.p_double[nless+neq];
                }
            }
            else
            {
                cursplit = 0.5*(x->ptr.p_double[nless-1]+x->ptr.p_double[nless]);
                nleft = nless;
                if( ae_fp_less_eq(cursplit,x->ptr.p_double[nless-1]) )
                {
                    cursplit = x->ptr.p_double[nless];
                }
            }
            *info = 1;
            cure = 0;
            for(i=0; i<=2*nc-1; i++)
            {
                cntbuf->ptr.p_int[i] = 0;
            }
            for(i=0; i<=nleft-1; i++)
            {
                cntbuf->ptr.p_int[c->ptr.p_int[i]] = cntbuf->ptr.p_int[c->ptr.p_int[i]]+1;
            }
            for(i=nleft; i<=n-1; i++)
            {
                cntbuf->ptr.p_int[nc+c->ptr.p_int[i]] = cntbuf->ptr.p_int[nc+c->ptr.p_int[i]]+1;
            }
            sl = nleft;
            sr = n-nleft;
            v = 0;
            for(i=0; i<=nc-1; i++)
            {
                w = cntbuf->ptr.p_int[i];
                v = v+w*ae_sqr(w/sl-1, _state);
                v = v+(sl-w)*ae_sqr(w/sl, _state);
                w = cntbuf->ptr.p_int[nc+i];
                v = v+w*ae_sqr(w/sr-1, _state);
                v = v+(sr-w)*ae_sqr(w/sr, _state);
            }
            cure = ae_sqrt(v/(nc*n), _state);
            if( ae_fp_less(cure,*e) )
            {
                *threshold = cursplit;
                *e = cure;
            }
        }
    }
}


/*************************************************************************
Makes split on attribute
*************************************************************************/
static void dforest_dfsplitr(/* Real    */ ae_vector* x,
     /* Real    */ ae_vector* y,
     ae_int_t n,
     ae_int_t flags,
     ae_int_t* info,
     double* threshold,
     double* e,
     /* Real    */ ae_vector* sortrbuf,
     /* Real    */ ae_vector* sortrbuf2,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t neq;
    ae_int_t nless;
    ae_int_t ngreater;
    ae_int_t q;
    ae_int_t qmin;
    ae_int_t qmax;
    ae_int_t qcnt;
    double cursplit;
    ae_int_t nleft;
    double v;
    double cure;

    *info = 0;
    *threshold = 0;
    *e = 0;

    tagsortfastr(x, y, sortrbuf, sortrbuf2, n, _state);
    *e = ae_maxrealnumber;
    *threshold = 0.5*(x->ptr.p_double[0]+x->ptr.p_double[n-1]);
    *info = -3;
    if( flags/dforest_dfusestrongsplits%2==0 )
    {
        
        /*
         * weak splits, split at half
         */
        qcnt = 2;
        qmin = 1;
        qmax = 1;
    }
    else
    {
        
        /*
         * strong splits: choose best quartile
         */
        qcnt = 4;
        qmin = 1;
        qmax = 3;
    }
    for(q=qmin; q<=qmax; q++)
    {
        cursplit = x->ptr.p_double[n*q/qcnt];
        neq = 0;
        nless = 0;
        ngreater = 0;
        for(i=0; i<=n-1; i++)
        {
            if( ae_fp_less(x->ptr.p_double[i],cursplit) )
            {
                nless = nless+1;
            }
            if( ae_fp_eq(x->ptr.p_double[i],cursplit) )
            {
                neq = neq+1;
            }
            if( ae_fp_greater(x->ptr.p_double[i],cursplit) )
            {
                ngreater = ngreater+1;
            }
        }
        ae_assert(neq!=0, "DFSplitR: NEq=0, something strange!!!", _state);
        if( nless!=0||ngreater!=0 )
        {
            
            /*
             * set threshold between two partitions, with
             * some tweaking to avoid problems with floating point
             * arithmetics.
             *
             * The problem is that when you calculates C = 0.5*(A+B) there
             * can be no C which lies strictly between A and B (for example,
             * there is no floating point number which is
             * greater than 1 and less than 1+eps). In such situations
             * we choose right side as theshold (remember that
             * points which lie on threshold falls to the right side).
             */
            if( nless<ngreater )
            {
                cursplit = 0.5*(x->ptr.p_double[nless+neq-1]+x->ptr.p_double[nless+neq]);
                nleft = nless+neq;
                if( ae_fp_less_eq(cursplit,x->ptr.p_double[nless+neq-1]) )
                {
                    cursplit = x->ptr.p_double[nless+neq];
                }
            }
            else
            {
                cursplit = 0.5*(x->ptr.p_double[nless-1]+x->ptr.p_double[nless]);
                nleft = nless;
                if( ae_fp_less_eq(cursplit,x->ptr.p_double[nless-1]) )
                {
                    cursplit = x->ptr.p_double[nless];
                }
            }
            *info = 1;
            cure = 0;
            v = 0;
            for(i=0; i<=nleft-1; i++)
            {
                v = v+y->ptr.p_double[i];
            }
            v = v/nleft;
            for(i=0; i<=nleft-1; i++)
            {
                cure = cure+ae_sqr(y->ptr.p_double[i]-v, _state);
            }
            v = 0;
            for(i=nleft; i<=n-1; i++)
            {
                v = v+y->ptr.p_double[i];
            }
            v = v/(n-nleft);
            for(i=nleft; i<=n-1; i++)
            {
                cure = cure+ae_sqr(y->ptr.p_double[i]-v, _state);
            }
            cure = ae_sqrt(cure/n, _state);
            if( ae_fp_less(cure,*e) )
            {
                *threshold = cursplit;
                *e = cure;
            }
        }
    }
}


ae_bool _decisionforest_init(void* _p, ae_state *_state, ae_bool make_automatic)
{
    decisionforest *p = (decisionforest*)_p;
    ae_touch_ptr((void*)p);
    if( !ae_vector_init(&p->trees, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    return ae_true;
}


ae_bool _decisionforest_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic)
{
    decisionforest *dst = (decisionforest*)_dst;
    decisionforest *src = (decisionforest*)_src;
    dst->nvars = src->nvars;
    dst->nclasses = src->nclasses;
    dst->ntrees = src->ntrees;
    dst->bufsize = src->bufsize;
    if( !ae_vector_init_copy(&dst->trees, &src->trees, _state, make_automatic) )
        return ae_false;
    return ae_true;
}


void _decisionforest_clear(void* _p)
{
    decisionforest *p = (decisionforest*)_p;
    ae_touch_ptr((void*)p);
    ae_vector_clear(&p->trees);
}


void _decisionforest_destroy(void* _p)
{
    decisionforest *p = (decisionforest*)_p;
    ae_touch_ptr((void*)p);
    ae_vector_destroy(&p->trees);
}


ae_bool _dfreport_init(void* _p, ae_state *_state, ae_bool make_automatic)
{
    dfreport *p = (dfreport*)_p;
    ae_touch_ptr((void*)p);
    return ae_true;
}


ae_bool _dfreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic)
{
    dfreport *dst = (dfreport*)_dst;
    dfreport *src = (dfreport*)_src;
    dst->relclserror = src->relclserror;
    dst->avgce = src->avgce;
    dst->rmserror = src->rmserror;
    dst->avgerror = src->avgerror;
    dst->avgrelerror = src->avgrelerror;
    dst->oobrelclserror = src->oobrelclserror;
    dst->oobavgce = src->oobavgce;
    dst->oobrmserror = src->oobrmserror;
    dst->oobavgerror = src->oobavgerror;
    dst->oobavgrelerror = src->oobavgrelerror;
    return ae_true;
}


void _dfreport_clear(void* _p)
{
    dfreport *p = (dfreport*)_p;
    ae_touch_ptr((void*)p);
}


void _dfreport_destroy(void* _p)
{
    dfreport *p = (dfreport*)_p;
    ae_touch_ptr((void*)p);
}


ae_bool _dfinternalbuffers_init(void* _p, ae_state *_state, ae_bool make_automatic)
{
    dfinternalbuffers *p = (dfinternalbuffers*)_p;
    ae_touch_ptr((void*)p);
    if( !ae_vector_init(&p->treebuf, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->idxbuf, 0, DT_INT, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->tmpbufr, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->tmpbufr2, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->tmpbufi, 0, DT_INT, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->classibuf, 0, DT_INT, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->sortrbuf, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->sortrbuf2, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->sortibuf, 0, DT_INT, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->varpool, 0, DT_INT, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->evsbin, 0, DT_BOOL, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->evssplits, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    return ae_true;
}


ae_bool _dfinternalbuffers_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic)
{
    dfinternalbuffers *dst = (dfinternalbuffers*)_dst;
    dfinternalbuffers *src = (dfinternalbuffers*)_src;
    if( !ae_vector_init_copy(&dst->treebuf, &src->treebuf, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->idxbuf, &src->idxbuf, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->tmpbufr, &src->tmpbufr, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->tmpbufr2, &src->tmpbufr2, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->tmpbufi, &src->tmpbufi, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->classibuf, &src->classibuf, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->sortrbuf, &src->sortrbuf, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->sortrbuf2, &src->sortrbuf2, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->sortibuf, &src->sortibuf, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->varpool, &src->varpool, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->evsbin, &src->evsbin, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->evssplits, &src->evssplits, _state, make_automatic) )
        return ae_false;
    return ae_true;
}


void _dfinternalbuffers_clear(void* _p)
{
    dfinternalbuffers *p = (dfinternalbuffers*)_p;
    ae_touch_ptr((void*)p);
    ae_vector_clear(&p->treebuf);
    ae_vector_clear(&p->idxbuf);
    ae_vector_clear(&p->tmpbufr);
    ae_vector_clear(&p->tmpbufr2);
    ae_vector_clear(&p->tmpbufi);
    ae_vector_clear(&p->classibuf);
    ae_vector_clear(&p->sortrbuf);
    ae_vector_clear(&p->sortrbuf2);
    ae_vector_clear(&p->sortibuf);
    ae_vector_clear(&p->varpool);
    ae_vector_clear(&p->evsbin);
    ae_vector_clear(&p->evssplits);
}


void _dfinternalbuffers_destroy(void* _p)
{
    dfinternalbuffers *p = (dfinternalbuffers*)_p;
    ae_touch_ptr((void*)p);
    ae_vector_destroy(&p->treebuf);
    ae_vector_destroy(&p->idxbuf);
    ae_vector_destroy(&p->tmpbufr);
    ae_vector_destroy(&p->tmpbufr2);
    ae_vector_destroy(&p->tmpbufi);
    ae_vector_destroy(&p->classibuf);
    ae_vector_destroy(&p->sortrbuf);
    ae_vector_destroy(&p->sortrbuf2);
    ae_vector_destroy(&p->sortibuf);
    ae_vector_destroy(&p->varpool);
    ae_vector_destroy(&p->evsbin);
    ae_vector_destroy(&p->evssplits);
}




/*************************************************************************
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


  -- ALGLIB --
     Copyright 02.08.2008 by Bochkanov Sergey
*************************************************************************/
void lrbuild(/* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_int_t nvars,
     ae_int_t* info,
     linearmodel* lm,
     lrreport* ar,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_vector s;
    ae_int_t i;
    double sigma2;

    ae_frame_make(_state, &_frame_block);
    *info = 0;
    _linearmodel_clear(lm);
    _lrreport_clear(ar);
    ae_vector_init(&s, 0, DT_REAL, _state, ae_true);

    if( npoints<=nvars+1||nvars<1 )
    {
        *info = -1;
        ae_frame_leave(_state);
        return;
    }
    ae_vector_set_length(&s, npoints-1+1, _state);
    for(i=0; i<=npoints-1; i++)
    {
        s.ptr.p_double[i] = 1;
    }
    lrbuilds(xy, &s, npoints, nvars, info, lm, ar, _state);
    if( *info<0 )
    {
        ae_frame_leave(_state);
        return;
    }
    sigma2 = ae_sqr(ar->rmserror, _state)*npoints/(npoints-nvars-1);
    for(i=0; i<=nvars; i++)
    {
        ae_v_muld(&ar->c.ptr.pp_double[i][0], 1, ae_v_len(0,nvars), sigma2);
    }
    ae_frame_leave(_state);
}


/*************************************************************************
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


  -- ALGLIB --
     Copyright 02.08.2008 by Bochkanov Sergey
*************************************************************************/
void lrbuilds(/* Real    */ ae_matrix* xy,
     /* Real    */ ae_vector* s,
     ae_int_t npoints,
     ae_int_t nvars,
     ae_int_t* info,
     linearmodel* lm,
     lrreport* ar,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_matrix xyi;
    ae_vector x;
    ae_vector means;
    ae_vector sigmas;
    ae_int_t i;
    ae_int_t j;
    double v;
    ae_int_t offs;
    double mean;
    double variance;
    double skewness;
    double kurtosis;

    ae_frame_make(_state, &_frame_block);
    *info = 0;
    _linearmodel_clear(lm);
    _lrreport_clear(ar);
    ae_matrix_init(&xyi, 0, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&x, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&means, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&sigmas, 0, DT_REAL, _state, ae_true);

    
    /*
     * Test parameters
     */
    if( npoints<=nvars+1||nvars<1 )
    {
        *info = -1;
        ae_frame_leave(_state);
        return;
    }
    
    /*
     * Copy data, add one more column (constant term)
     */
    ae_matrix_set_length(&xyi, npoints-1+1, nvars+1+1, _state);
    for(i=0; i<=npoints-1; i++)
    {
        ae_v_move(&xyi.ptr.pp_double[i][0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1));
        xyi.ptr.pp_double[i][nvars] = 1;
        xyi.ptr.pp_double[i][nvars+1] = xy->ptr.pp_double[i][nvars];
    }
    
    /*
     * Standartization
     */
    ae_vector_set_length(&x, npoints-1+1, _state);
    ae_vector_set_length(&means, nvars-1+1, _state);
    ae_vector_set_length(&sigmas, nvars-1+1, _state);
    for(j=0; j<=nvars-1; j++)
    {
        ae_v_move(&x.ptr.p_double[0], 1, &xy->ptr.pp_double[0][j], xy->stride, ae_v_len(0,npoints-1));
        samplemoments(&x, npoints, &mean, &variance, &skewness, &kurtosis, _state);
        means.ptr.p_double[j] = mean;
        sigmas.ptr.p_double[j] = ae_sqrt(variance, _state);
        if( ae_fp_eq(sigmas.ptr.p_double[j],0) )
        {
            sigmas.ptr.p_double[j] = 1;
        }
        for(i=0; i<=npoints-1; i++)
        {
            xyi.ptr.pp_double[i][j] = (xyi.ptr.pp_double[i][j]-means.ptr.p_double[j])/sigmas.ptr.p_double[j];
        }
    }
    
    /*
     * Internal processing
     */
    linreg_lrinternal(&xyi, s, npoints, nvars+1, info, lm, ar, _state);
    if( *info<0 )
    {
        ae_frame_leave(_state);
        return;
    }
    
    /*
     * Un-standartization
     */
    offs = ae_round(lm->w.ptr.p_double[3], _state);
    for(j=0; j<=nvars-1; j++)
    {
        
        /*
         * Constant term is updated (and its covariance too,
         * since it gets some variance from J-th component)
         */
        lm->w.ptr.p_double[offs+nvars] = lm->w.ptr.p_double[offs+nvars]-lm->w.ptr.p_double[offs+j]*means.ptr.p_double[j]/sigmas.ptr.p_double[j];
        v = means.ptr.p_double[j]/sigmas.ptr.p_double[j];
        ae_v_subd(&ar->c.ptr.pp_double[nvars][0], 1, &ar->c.ptr.pp_double[j][0], 1, ae_v_len(0,nvars), v);
        ae_v_subd(&ar->c.ptr.pp_double[0][nvars], ar->c.stride, &ar->c.ptr.pp_double[0][j], ar->c.stride, ae_v_len(0,nvars), v);
        
        /*
         * J-th term is updated
         */
        lm->w.ptr.p_double[offs+j] = lm->w.ptr.p_double[offs+j]/sigmas.ptr.p_double[j];
        v = 1/sigmas.ptr.p_double[j];
        ae_v_muld(&ar->c.ptr.pp_double[j][0], 1, ae_v_len(0,nvars), v);
        ae_v_muld(&ar->c.ptr.pp_double[0][j], ar->c.stride, ae_v_len(0,nvars), v);
    }
    ae_frame_leave(_state);
}


/*************************************************************************
Like LRBuildS, but builds model

    Y = A(0)*X[0] + ... + A(N-1)*X[N-1]

i.e. with zero constant term.

  -- ALGLIB --
     Copyright 30.10.2008 by Bochkanov Sergey
*************************************************************************/
void lrbuildzs(/* Real    */ ae_matrix* xy,
     /* Real    */ ae_vector* s,
     ae_int_t npoints,
     ae_int_t nvars,
     ae_int_t* info,
     linearmodel* lm,
     lrreport* ar,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_matrix xyi;
    ae_vector x;
    ae_vector c;
    ae_int_t i;
    ae_int_t j;
    double v;
    ae_int_t offs;
    double mean;
    double variance;
    double skewness;
    double kurtosis;

    ae_frame_make(_state, &_frame_block);
    *info = 0;
    _linearmodel_clear(lm);
    _lrreport_clear(ar);
    ae_matrix_init(&xyi, 0, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&x, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&c, 0, DT_REAL, _state, ae_true);

    
    /*
     * Test parameters
     */
    if( npoints<=nvars+1||nvars<1 )
    {
        *info = -1;
        ae_frame_leave(_state);
        return;
    }
    
    /*
     * Copy data, add one more column (constant term)
     */
    ae_matrix_set_length(&xyi, npoints-1+1, nvars+1+1, _state);
    for(i=0; i<=npoints-1; i++)
    {
        ae_v_move(&xyi.ptr.pp_double[i][0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1));
        xyi.ptr.pp_double[i][nvars] = 0;
        xyi.ptr.pp_double[i][nvars+1] = xy->ptr.pp_double[i][nvars];
    }
    
    /*
     * Standartization: unusual scaling
     */
    ae_vector_set_length(&x, npoints-1+1, _state);
    ae_vector_set_length(&c, nvars-1+1, _state);
    for(j=0; j<=nvars-1; j++)
    {
        ae_v_move(&x.ptr.p_double[0], 1, &xy->ptr.pp_double[0][j], xy->stride, ae_v_len(0,npoints-1));
        samplemoments(&x, npoints, &mean, &variance, &skewness, &kurtosis, _state);
        if( ae_fp_greater(ae_fabs(mean, _state),ae_sqrt(variance, _state)) )
        {
            
            /*
             * variation is relatively small, it is better to
             * bring mean value to 1
             */
            c.ptr.p_double[j] = mean;
        }
        else
        {
            
            /*
             * variation is large, it is better to bring variance to 1
             */
            if( ae_fp_eq(variance,0) )
            {
                variance = 1;
            }
            c.ptr.p_double[j] = ae_sqrt(variance, _state);
        }
        for(i=0; i<=npoints-1; i++)
        {
            xyi.ptr.pp_double[i][j] = xyi.ptr.pp_double[i][j]/c.ptr.p_double[j];
        }
    }
    
    /*
     * Internal processing
     */
    linreg_lrinternal(&xyi, s, npoints, nvars+1, info, lm, ar, _state);
    if( *info<0 )
    {
        ae_frame_leave(_state);
        return;
    }
    
    /*
     * Un-standartization
     */
    offs = ae_round(lm->w.ptr.p_double[3], _state);
    for(j=0; j<=nvars-1; j++)
    {
        
        /*
         * J-th term is updated
         */
        lm->w.ptr.p_double[offs+j] = lm->w.ptr.p_double[offs+j]/c.ptr.p_double[j];
        v = 1/c.ptr.p_double[j];
        ae_v_muld(&ar->c.ptr.pp_double[j][0], 1, ae_v_len(0,nvars), v);
        ae_v_muld(&ar->c.ptr.pp_double[0][j], ar->c.stride, ae_v_len(0,nvars), v);
    }
    ae_frame_leave(_state);
}


/*************************************************************************
Like LRBuild but builds model

    Y = A(0)*X[0] + ... + A(N-1)*X[N-1]

i.e. with zero constant term.

  -- ALGLIB --
     Copyright 30.10.2008 by Bochkanov Sergey
*************************************************************************/
void lrbuildz(/* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_int_t nvars,
     ae_int_t* info,
     linearmodel* lm,
     lrreport* ar,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_vector s;
    ae_int_t i;
    double sigma2;

    ae_frame_make(_state, &_frame_block);
    *info = 0;
    _linearmodel_clear(lm);
    _lrreport_clear(ar);
    ae_vector_init(&s, 0, DT_REAL, _state, ae_true);

    if( npoints<=nvars+1||nvars<1 )
    {
        *info = -1;
        ae_frame_leave(_state);
        return;
    }
    ae_vector_set_length(&s, npoints-1+1, _state);
    for(i=0; i<=npoints-1; i++)
    {
        s.ptr.p_double[i] = 1;
    }
    lrbuildzs(xy, &s, npoints, nvars, info, lm, ar, _state);
    if( *info<0 )
    {
        ae_frame_leave(_state);
        return;
    }
    sigma2 = ae_sqr(ar->rmserror, _state)*npoints/(npoints-nvars-1);
    for(i=0; i<=nvars; i++)
    {
        ae_v_muld(&ar->c.ptr.pp_double[i][0], 1, ae_v_len(0,nvars), sigma2);
    }
    ae_frame_leave(_state);
}


/*************************************************************************
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)

  -- ALGLIB --
     Copyright 30.08.2008 by Bochkanov Sergey
*************************************************************************/
void lrunpack(linearmodel* lm,
     /* Real    */ ae_vector* v,
     ae_int_t* nvars,
     ae_state *_state)
{
    ae_int_t offs;

    ae_vector_clear(v);
    *nvars = 0;

    ae_assert(ae_round(lm->w.ptr.p_double[1], _state)==linreg_lrvnum, "LINREG: Incorrect LINREG version!", _state);
    *nvars = ae_round(lm->w.ptr.p_double[2], _state);
    offs = ae_round(lm->w.ptr.p_double[3], _state);
    ae_vector_set_length(v, *nvars+1, _state);
    ae_v_move(&v->ptr.p_double[0], 1, &lm->w.ptr.p_double[offs], 1, ae_v_len(0,*nvars));
}


/*************************************************************************
"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.

  -- ALGLIB --
     Copyright 30.08.2008 by Bochkanov Sergey
*************************************************************************/
void lrpack(/* Real    */ ae_vector* v,
     ae_int_t nvars,
     linearmodel* lm,
     ae_state *_state)
{
    ae_int_t offs;

    _linearmodel_clear(lm);

    ae_vector_set_length(&lm->w, 4+nvars+1, _state);
    offs = 4;
    lm->w.ptr.p_double[0] = 4+nvars+1;
    lm->w.ptr.p_double[1] = linreg_lrvnum;
    lm->w.ptr.p_double[2] = nvars;
    lm->w.ptr.p_double[3] = offs;
    ae_v_move(&lm->w.ptr.p_double[offs], 1, &v->ptr.p_double[0], 1, ae_v_len(offs,offs+nvars));
}


/*************************************************************************
Procesing

INPUT PARAMETERS:
    LM      -   linear model
    X       -   input vector,  array[0..NVars-1].

Result:
    value of linear model regression estimate

  -- ALGLIB --
     Copyright 03.09.2008 by Bochkanov Sergey
*************************************************************************/
double lrprocess(linearmodel* lm,
     /* Real    */ ae_vector* x,
     ae_state *_state)
{
    double v;
    ae_int_t offs;
    ae_int_t nvars;
    double result;


    ae_assert(ae_round(lm->w.ptr.p_double[1], _state)==linreg_lrvnum, "LINREG: Incorrect LINREG version!", _state);
    nvars = ae_round(lm->w.ptr.p_double[2], _state);
    offs = ae_round(lm->w.ptr.p_double[3], _state);
    v = ae_v_dotproduct(&x->ptr.p_double[0], 1, &lm->w.ptr.p_double[offs], 1, ae_v_len(0,nvars-1));
    result = v+lm->w.ptr.p_double[offs+nvars];
    return result;
}


/*************************************************************************
RMS error on the test set

INPUT PARAMETERS:
    LM      -   linear model
    XY      -   test set
    NPoints -   test set size

RESULT:
    root mean square error.

  -- ALGLIB --
     Copyright 30.08.2008 by Bochkanov Sergey
*************************************************************************/
double lrrmserror(linearmodel* lm,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_state *_state)
{
    ae_int_t i;
    double v;
    ae_int_t offs;
    ae_int_t nvars;
    double result;


    ae_assert(ae_round(lm->w.ptr.p_double[1], _state)==linreg_lrvnum, "LINREG: Incorrect LINREG version!", _state);
    nvars = ae_round(lm->w.ptr.p_double[2], _state);
    offs = ae_round(lm->w.ptr.p_double[3], _state);
    result = 0;
    for(i=0; i<=npoints-1; i++)
    {
        v = ae_v_dotproduct(&xy->ptr.pp_double[i][0], 1, &lm->w.ptr.p_double[offs], 1, ae_v_len(0,nvars-1));
        v = v+lm->w.ptr.p_double[offs+nvars];
        result = result+ae_sqr(v-xy->ptr.pp_double[i][nvars], _state);
    }
    result = ae_sqrt(result/npoints, _state);
    return result;
}


/*************************************************************************
Average error on the test set

INPUT PARAMETERS:
    LM      -   linear model
    XY      -   test set
    NPoints -   test set size

RESULT:
    average error.

  -- ALGLIB --
     Copyright 30.08.2008 by Bochkanov Sergey
*************************************************************************/
double lravgerror(linearmodel* lm,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_state *_state)
{
    ae_int_t i;
    double v;
    ae_int_t offs;
    ae_int_t nvars;
    double result;


    ae_assert(ae_round(lm->w.ptr.p_double[1], _state)==linreg_lrvnum, "LINREG: Incorrect LINREG version!", _state);
    nvars = ae_round(lm->w.ptr.p_double[2], _state);
    offs = ae_round(lm->w.ptr.p_double[3], _state);
    result = 0;
    for(i=0; i<=npoints-1; i++)
    {
        v = ae_v_dotproduct(&xy->ptr.pp_double[i][0], 1, &lm->w.ptr.p_double[offs], 1, ae_v_len(0,nvars-1));
        v = v+lm->w.ptr.p_double[offs+nvars];
        result = result+ae_fabs(v-xy->ptr.pp_double[i][nvars], _state);
    }
    result = result/npoints;
    return result;
}


/*************************************************************************
RMS error on the test set

INPUT PARAMETERS:
    LM      -   linear model
    XY      -   test set
    NPoints -   test set size

RESULT:
    average relative error.

  -- ALGLIB --
     Copyright 30.08.2008 by Bochkanov Sergey
*************************************************************************/
double lravgrelerror(linearmodel* lm,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t k;
    double v;
    ae_int_t offs;
    ae_int_t nvars;
    double result;


    ae_assert(ae_round(lm->w.ptr.p_double[1], _state)==linreg_lrvnum, "LINREG: Incorrect LINREG version!", _state);
    nvars = ae_round(lm->w.ptr.p_double[2], _state);
    offs = ae_round(lm->w.ptr.p_double[3], _state);
    result = 0;
    k = 0;
    for(i=0; i<=npoints-1; i++)
    {
        if( ae_fp_neq(xy->ptr.pp_double[i][nvars],0) )
        {
            v = ae_v_dotproduct(&xy->ptr.pp_double[i][0], 1, &lm->w.ptr.p_double[offs], 1, ae_v_len(0,nvars-1));
            v = v+lm->w.ptr.p_double[offs+nvars];
            result = result+ae_fabs((v-xy->ptr.pp_double[i][nvars])/xy->ptr.pp_double[i][nvars], _state);
            k = k+1;
        }
    }
    if( k!=0 )
    {
        result = result/k;
    }
    return result;
}


/*************************************************************************
Copying of LinearModel strucure

INPUT PARAMETERS:
    LM1 -   original

OUTPUT PARAMETERS:
    LM2 -   copy

  -- ALGLIB --
     Copyright 15.03.2009 by Bochkanov Sergey
*************************************************************************/
void lrcopy(linearmodel* lm1, linearmodel* lm2, ae_state *_state)
{
    ae_int_t k;

    _linearmodel_clear(lm2);

    k = ae_round(lm1->w.ptr.p_double[0], _state);
    ae_vector_set_length(&lm2->w, k-1+1, _state);
    ae_v_move(&lm2->w.ptr.p_double[0], 1, &lm1->w.ptr.p_double[0], 1, ae_v_len(0,k-1));
}


void lrlines(/* Real    */ ae_matrix* xy,
     /* Real    */ ae_vector* s,
     ae_int_t n,
     ae_int_t* info,
     double* a,
     double* b,
     double* vara,
     double* varb,
     double* covab,
     double* corrab,
     double* p,
     ae_state *_state)
{
    ae_int_t i;
    double ss;
    double sx;
    double sxx;
    double sy;
    double stt;
    double e1;
    double e2;
    double t;
    double chi2;

    *info = 0;
    *a = 0;
    *b = 0;
    *vara = 0;
    *varb = 0;
    *covab = 0;
    *corrab = 0;
    *p = 0;

    if( n<2 )
    {
        *info = -1;
        return;
    }
    for(i=0; i<=n-1; i++)
    {
        if( ae_fp_less_eq(s->ptr.p_double[i],0) )
        {
            *info = -2;
            return;
        }
    }
    *info = 1;
    
    /*
     * Calculate S, SX, SY, SXX
     */
    ss = 0;
    sx = 0;
    sy = 0;
    sxx = 0;
    for(i=0; i<=n-1; i++)
    {
        t = ae_sqr(s->ptr.p_double[i], _state);
        ss = ss+1/t;
        sx = sx+xy->ptr.pp_double[i][0]/t;
        sy = sy+xy->ptr.pp_double[i][1]/t;
        sxx = sxx+ae_sqr(xy->ptr.pp_double[i][0], _state)/t;
    }
    
    /*
     * Test for condition number
     */
    t = ae_sqrt(4*ae_sqr(sx, _state)+ae_sqr(ss-sxx, _state), _state);
    e1 = 0.5*(ss+sxx+t);
    e2 = 0.5*(ss+sxx-t);
    if( ae_fp_less_eq(ae_minreal(e1, e2, _state),1000*ae_machineepsilon*ae_maxreal(e1, e2, _state)) )
    {
        *info = -3;
        return;
    }
    
    /*
     * Calculate A, B
     */
    *a = 0;
    *b = 0;
    stt = 0;
    for(i=0; i<=n-1; i++)
    {
        t = (xy->ptr.pp_double[i][0]-sx/ss)/s->ptr.p_double[i];
        *b = *b+t*xy->ptr.pp_double[i][1]/s->ptr.p_double[i];
        stt = stt+ae_sqr(t, _state);
    }
    *b = *b/stt;
    *a = (sy-sx*(*b))/ss;
    
    /*
     * Calculate goodness-of-fit
     */
    if( n>2 )
    {
        chi2 = 0;
        for(i=0; i<=n-1; i++)
        {
            chi2 = chi2+ae_sqr((xy->ptr.pp_double[i][1]-(*a)-*b*xy->ptr.pp_double[i][0])/s->ptr.p_double[i], _state);
        }
        *p = incompletegammac((double)(n-2)/(double)2, chi2/2, _state);
    }
    else
    {
        *p = 1;
    }
    
    /*
     * Calculate other parameters
     */
    *vara = (1+ae_sqr(sx, _state)/(ss*stt))/ss;
    *varb = 1/stt;
    *covab = -sx/(ss*stt);
    *corrab = *covab/ae_sqrt(*vara*(*varb), _state);
}


void lrline(/* Real    */ ae_matrix* xy,
     ae_int_t n,
     ae_int_t* info,
     double* a,
     double* b,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_vector s;
    ae_int_t i;
    double vara;
    double varb;
    double covab;
    double corrab;
    double p;

    ae_frame_make(_state, &_frame_block);
    *info = 0;
    *a = 0;
    *b = 0;
    ae_vector_init(&s, 0, DT_REAL, _state, ae_true);

    if( n<2 )
    {
        *info = -1;
        ae_frame_leave(_state);
        return;
    }
    ae_vector_set_length(&s, n-1+1, _state);
    for(i=0; i<=n-1; i++)
    {
        s.ptr.p_double[i] = 1;
    }
    lrlines(xy, &s, n, info, a, b, &vara, &varb, &covab, &corrab, &p, _state);
    ae_frame_leave(_state);
}


/*************************************************************************
Internal linear regression subroutine
*************************************************************************/
static void linreg_lrinternal(/* Real    */ ae_matrix* xy,
     /* Real    */ ae_vector* s,
     ae_int_t npoints,
     ae_int_t nvars,
     ae_int_t* info,
     linearmodel* lm,
     lrreport* ar,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_matrix a;
    ae_matrix u;
    ae_matrix vt;
    ae_matrix vm;
    ae_matrix xym;
    ae_vector b;
    ae_vector sv;
    ae_vector t;
    ae_vector svi;
    ae_vector work;
    ae_int_t i;
    ae_int_t j;
    ae_int_t k;
    ae_int_t ncv;
    ae_int_t na;
    ae_int_t nacv;
    double r;
    double p;
    double epstol;
    lrreport ar2;
    ae_int_t offs;
    linearmodel tlm;

    ae_frame_make(_state, &_frame_block);
    *info = 0;
    _linearmodel_clear(lm);
    _lrreport_clear(ar);
    ae_matrix_init(&a, 0, 0, DT_REAL, _state, ae_true);
    ae_matrix_init(&u, 0, 0, DT_REAL, _state, ae_true);
    ae_matrix_init(&vt, 0, 0, DT_REAL, _state, ae_true);
    ae_matrix_init(&vm, 0, 0, DT_REAL, _state, ae_true);
    ae_matrix_init(&xym, 0, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&b, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&sv, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&t, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&svi, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&work, 0, DT_REAL, _state, ae_true);
    _lrreport_init(&ar2, _state, ae_true);
    _linearmodel_init(&tlm, _state, ae_true);

    epstol = 1000;
    
    /*
     * Check for errors in data
     */
    if( npoints<nvars||nvars<1 )
    {
        *info = -1;
        ae_frame_leave(_state);
        return;
    }
    for(i=0; i<=npoints-1; i++)
    {
        if( ae_fp_less_eq(s->ptr.p_double[i],0) )
        {
            *info = -2;
            ae_frame_leave(_state);
            return;
        }
    }
    *info = 1;
    
    /*
     * Create design matrix
     */
    ae_matrix_set_length(&a, npoints-1+1, nvars-1+1, _state);
    ae_vector_set_length(&b, npoints-1+1, _state);
    for(i=0; i<=npoints-1; i++)
    {
        r = 1/s->ptr.p_double[i];
        ae_v_moved(&a.ptr.pp_double[i][0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1), r);
        b.ptr.p_double[i] = xy->ptr.pp_double[i][nvars]/s->ptr.p_double[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
     */
    ae_vector_set_length(&lm->w, 4+nvars-1+1, _state);
    offs = 4;
    lm->w.ptr.p_double[0] = 4+nvars;
    lm->w.ptr.p_double[1] = linreg_lrvnum;
    lm->w.ptr.p_double[2] = nvars-1;
    lm->w.ptr.p_double[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
     */
    ae_vector_set_length(&t, nvars-1+1, _state);
    ae_vector_set_length(&svi, nvars-1+1, _state);
    ae_matrix_set_length(&ar->c, nvars-1+1, nvars-1+1, _state);
    ae_matrix_set_length(&vm, nvars-1+1, nvars-1+1, _state);
    if( !rmatrixsvd(&a, npoints, nvars, 1, 1, 2, &sv, &u, &vt, _state) )
    {
        *info = -4;
        ae_frame_leave(_state);
        return;
    }
    if( ae_fp_less_eq(sv.ptr.p_double[0],0) )
    {
        
        /*
         * Degenerate case: zero design matrix.
         */
        for(i=offs; i<=offs+nvars-1; i++)
        {
            lm->w.ptr.p_double[i] = 0;
        }
        ar->rmserror = lrrmserror(lm, xy, npoints, _state);
        ar->avgerror = lravgerror(lm, xy, npoints, _state);
        ar->avgrelerror = lravgrelerror(lm, xy, npoints, _state);
        ar->cvrmserror = ar->rmserror;
        ar->cvavgerror = ar->avgerror;
        ar->cvavgrelerror = ar->avgrelerror;
        ar->ncvdefects = 0;
        ae_vector_set_length(&ar->cvdefects, nvars-1+1, _state);
        ae_matrix_set_length(&ar->c, nvars-1+1, nvars-1+1, _state);
        for(i=0; i<=nvars-1; i++)
        {
            for(j=0; j<=nvars-1; j++)
            {
                ar->c.ptr.pp_double[i][j] = 0;
            }
        }
        ae_frame_leave(_state);
        return;
    }
    if( ae_fp_less_eq(sv.ptr.p_double[nvars-1],epstol*ae_machineepsilon*sv.ptr.p_double[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( ae_fp_greater(sv.ptr.p_double[k-1],epstol*ae_machineepsilon*sv.ptr.p_double[0]) )
            {
                
                /*
                 * Reduce
                 */
                ae_matrix_set_length(&xym, npoints-1+1, k+1, _state);
                for(i=0; i<=npoints-1; i++)
                {
                    for(j=0; j<=k-1; j++)
                    {
                        r = ae_v_dotproduct(&xy->ptr.pp_double[i][0], 1, &vt.ptr.pp_double[j][0], 1, ae_v_len(0,nvars-1));
                        xym.ptr.pp_double[i][j] = r;
                    }
                    xym.ptr.pp_double[i][k] = xy->ptr.pp_double[i][nvars];
                }
                
                /*
                 * Solve
                 */
                linreg_lrinternal(&xym, s, npoints, k, info, &tlm, &ar2, _state);
                if( *info!=1 )
                {
                    ae_frame_leave(_state);
                    return;
                }
                
                /*
                 * Convert back to un-reduced format
                 */
                for(j=0; j<=nvars-1; j++)
                {
                    lm->w.ptr.p_double[offs+j] = 0;
                }
                for(j=0; j<=k-1; j++)
                {
                    r = tlm.w.ptr.p_double[offs+j];
                    ae_v_addd(&lm->w.ptr.p_double[offs], 1, &vt.ptr.pp_double[j][0], 1, ae_v_len(offs,offs+nvars-1), r);
                }
                ar->rmserror = ar2.rmserror;
                ar->avgerror = ar2.avgerror;
                ar->avgrelerror = ar2.avgrelerror;
                ar->cvrmserror = ar2.cvrmserror;
                ar->cvavgerror = ar2.cvavgerror;
                ar->cvavgrelerror = ar2.cvavgrelerror;
                ar->ncvdefects = ar2.ncvdefects;
                ae_vector_set_length(&ar->cvdefects, nvars-1+1, _state);
                for(j=0; j<=ar->ncvdefects-1; j++)
                {
                    ar->cvdefects.ptr.p_int[j] = ar2.cvdefects.ptr.p_int[j];
                }
                ae_matrix_set_length(&ar->c, nvars-1+1, nvars-1+1, _state);
                ae_vector_set_length(&work, nvars+1, _state);
                matrixmatrixmultiply(&ar2.c, 0, k-1, 0, k-1, ae_false, &vt, 0, k-1, 0, nvars-1, ae_false, 1.0, &vm, 0, k-1, 0, nvars-1, 0.0, &work, _state);
                matrixmatrixmultiply(&vt, 0, k-1, 0, nvars-1, ae_true, &vm, 0, k-1, 0, nvars-1, ae_false, 1.0, &ar->c, 0, nvars-1, 0, nvars-1, 0.0, &work, _state);
                ae_frame_leave(_state);
                return;
            }
        }
        *info = -255;
        ae_frame_leave(_state);
        return;
    }
    for(i=0; i<=nvars-1; i++)
    {
        if( ae_fp_greater(sv.ptr.p_double[i],epstol*ae_machineepsilon*sv.ptr.p_double[0]) )
        {
            svi.ptr.p_double[i] = 1/sv.ptr.p_double[i];
        }
        else
        {
            svi.ptr.p_double[i] = 0;
        }
    }
    for(i=0; i<=nvars-1; i++)
    {
        t.ptr.p_double[i] = 0;
    }
    for(i=0; i<=npoints-1; i++)
    {
        r = b.ptr.p_double[i];
        ae_v_addd(&t.ptr.p_double[0], 1, &u.ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1), r);
    }
    for(i=0; i<=nvars-1; i++)
    {
        lm->w.ptr.p_double[offs+i] = 0;
    }
    for(i=0; i<=nvars-1; i++)
    {
        r = t.ptr.p_double[i]*svi.ptr.p_double[i];
        ae_v_addd(&lm->w.ptr.p_double[offs], 1, &vt.ptr.pp_double[i][0], 1, ae_v_len(offs,offs+nvars-1), r);
    }
    for(j=0; j<=nvars-1; j++)
    {
        r = svi.ptr.p_double[j];
        ae_v_moved(&vm.ptr.pp_double[0][j], vm.stride, &vt.ptr.pp_double[j][0], 1, ae_v_len(0,nvars-1), r);
    }
    for(i=0; i<=nvars-1; i++)
    {
        for(j=i; j<=nvars-1; j++)
        {
            r = ae_v_dotproduct(&vm.ptr.pp_double[i][0], 1, &vm.ptr.pp_double[j][0], 1, ae_v_len(0,nvars-1));
            ar->c.ptr.pp_double[i][j] = r;
            ar->c.ptr.pp_double[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->rmserror = 0;
    ar->avgerror = 0;
    ar->avgrelerror = 0;
    ar->cvrmserror = 0;
    ar->cvavgerror = 0;
    ar->cvavgrelerror = 0;
    ar->ncvdefects = 0;
    ae_vector_set_length(&ar->cvdefects, nvars-1+1, _state);
    for(i=0; i<=npoints-1; i++)
    {
        
        /*
         * Error on a training set
         */
        r = ae_v_dotproduct(&xy->ptr.pp_double[i][0], 1, &lm->w.ptr.p_double[offs], 1, ae_v_len(0,nvars-1));
        ar->rmserror = ar->rmserror+ae_sqr(r-xy->ptr.pp_double[i][nvars], _state);
        ar->avgerror = ar->avgerror+ae_fabs(r-xy->ptr.pp_double[i][nvars], _state);
        if( ae_fp_neq(xy->ptr.pp_double[i][nvars],0) )
        {
            ar->avgrelerror = ar->avgrelerror+ae_fabs((r-xy->ptr.pp_double[i][nvars])/xy->ptr.pp_double[i][nvars], _state);
            na = na+1;
        }
        
        /*
         * Error using fast leave-one-out cross-validation
         */
        p = ae_v_dotproduct(&u.ptr.pp_double[i][0], 1, &u.ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1));
        if( ae_fp_greater(p,1-epstol*ae_machineepsilon) )
        {
            ar->cvdefects.ptr.p_int[ar->ncvdefects] = i;
            ar->ncvdefects = ar->ncvdefects+1;
            continue;
        }
        r = s->ptr.p_double[i]*(r/s->ptr.p_double[i]-b.ptr.p_double[i]*p)/(1-p);
        ar->cvrmserror = ar->cvrmserror+ae_sqr(r-xy->ptr.pp_double[i][nvars], _state);
        ar->cvavgerror = ar->cvavgerror+ae_fabs(r-xy->ptr.pp_double[i][nvars], _state);
        if( ae_fp_neq(xy->ptr.pp_double[i][nvars],0) )
        {
            ar->cvavgrelerror = ar->cvavgrelerror+ae_fabs((r-xy->ptr.pp_double[i][nvars])/xy->ptr.pp_double[i][nvars], _state);
            nacv = nacv+1;
        }
        ncv = ncv+1;
    }
    if( ncv==0 )
    {
        
        /*
         * Something strange: ALL ui are degenerate.
         * Unexpected...
         */
        *info = -255;
        ae_frame_leave(_state);
        return;
    }
    ar->rmserror = ae_sqrt(ar->rmserror/npoints, _state);
    ar->avgerror = ar->avgerror/npoints;
    if( na!=0 )
    {
        ar->avgrelerror = ar->avgrelerror/na;
    }
    ar->cvrmserror = ae_sqrt(ar->cvrmserror/ncv, _state);
    ar->cvavgerror = ar->cvavgerror/ncv;
    if( nacv!=0 )
    {
        ar->cvavgrelerror = ar->cvavgrelerror/nacv;
    }
    ae_frame_leave(_state);
}


ae_bool _linearmodel_init(void* _p, ae_state *_state, ae_bool make_automatic)
{
    linearmodel *p = (linearmodel*)_p;
    ae_touch_ptr((void*)p);
    if( !ae_vector_init(&p->w, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    return ae_true;
}


ae_bool _linearmodel_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic)
{
    linearmodel *dst = (linearmodel*)_dst;
    linearmodel *src = (linearmodel*)_src;
    if( !ae_vector_init_copy(&dst->w, &src->w, _state, make_automatic) )
        return ae_false;
    return ae_true;
}


void _linearmodel_clear(void* _p)
{
    linearmodel *p = (linearmodel*)_p;
    ae_touch_ptr((void*)p);
    ae_vector_clear(&p->w);
}


void _linearmodel_destroy(void* _p)
{
    linearmodel *p = (linearmodel*)_p;
    ae_touch_ptr((void*)p);
    ae_vector_destroy(&p->w);
}


ae_bool _lrreport_init(void* _p, ae_state *_state, ae_bool make_automatic)
{
    lrreport *p = (lrreport*)_p;
    ae_touch_ptr((void*)p);
    if( !ae_matrix_init(&p->c, 0, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->cvdefects, 0, DT_INT, _state, make_automatic) )
        return ae_false;
    return ae_true;
}


ae_bool _lrreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic)
{
    lrreport *dst = (lrreport*)_dst;
    lrreport *src = (lrreport*)_src;
    if( !ae_matrix_init_copy(&dst->c, &src->c, _state, make_automatic) )
        return ae_false;
    dst->rmserror = src->rmserror;
    dst->avgerror = src->avgerror;
    dst->avgrelerror = src->avgrelerror;
    dst->cvrmserror = src->cvrmserror;
    dst->cvavgerror = src->cvavgerror;
    dst->cvavgrelerror = src->cvavgrelerror;
    dst->ncvdefects = src->ncvdefects;
    if( !ae_vector_init_copy(&dst->cvdefects, &src->cvdefects, _state, make_automatic) )
        return ae_false;
    return ae_true;
}


void _lrreport_clear(void* _p)
{
    lrreport *p = (lrreport*)_p;
    ae_touch_ptr((void*)p);
    ae_matrix_clear(&p->c);
    ae_vector_clear(&p->cvdefects);
}


void _lrreport_destroy(void* _p)
{
    lrreport *p = (lrreport*)_p;
    ae_touch_ptr((void*)p);
    ae_matrix_destroy(&p->c);
    ae_vector_destroy(&p->cvdefects);
}




/*************************************************************************
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.

  -- ALGLIB --
     Copyright 25.10.2011 by Bochkanov Sergey
*************************************************************************/
void filtersma(/* Real    */ ae_vector* x,
     ae_int_t n,
     ae_int_t k,
     ae_state *_state)
{
    ae_int_t i;
    double runningsum;
    double termsinsum;
    ae_int_t zeroprefix;
    double v;


    ae_assert(n>=0, "FilterSMA: N<0", _state);
    ae_assert(x->cnt>=n, "FilterSMA: Length(X)<N", _state);
    ae_assert(isfinitevector(x, n, _state), "FilterSMA: X contains INF or NAN", _state);
    ae_assert(k>=1, "FilterSMA: K<1", _state);
    
    /*
     * Quick exit, if necessary
     */
    if( n<=1||k==1 )
    {
        return;
    }
    
    /*
     * Prepare variables (see below for explanation)
     */
    runningsum = 0.0;
    termsinsum = 0;
    for(i=ae_maxint(n-k, 0, _state); i<=n-1; i++)
    {
        runningsum = runningsum+x->ptr.p_double[i];
        termsinsum = termsinsum+1;
    }
    i = ae_maxint(n-k, 0, _state);
    zeroprefix = 0;
    while(i<=n-1&&ae_fp_eq(x->ptr.p_double[i],0))
    {
        zeroprefix = zeroprefix+1;
        i = i+1;
    }
    
    /*
     * 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->ptr.p_double[i];
        x->ptr.p_double[i] = runningsum/termsinsum;
        
        /*
         * Update RunningSum and TermsInSum
         */
        if( i-k>=0 )
        {
            runningsum = runningsum-v+x->ptr.p_double[i-k];
        }
        else
        {
            runningsum = runningsum-v;
            termsinsum = termsinsum-1;
        }
        
        /*
         * Update ZeroPrefix.
         * In case we have ZeroPrefix=TermsInSum,
         * RunningSum is reset to zero.
         */
        if( i-k>=0 )
        {
            if( ae_fp_neq(x->ptr.p_double[i-k],0) )
            {
                zeroprefix = 0;
            }
            else
            {
                zeroprefix = ae_minint(zeroprefix+1, k, _state);
            }
        }
        else
        {
            zeroprefix = ae_minint(zeroprefix, i+1, _state);
        }
        if( ae_fp_eq(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).

  -- ALGLIB --
     Copyright 25.10.2011 by Bochkanov Sergey
*************************************************************************/
void filterema(/* Real    */ ae_vector* x,
     ae_int_t n,
     double alpha,
     ae_state *_state)
{
    ae_int_t i;


    ae_assert(n>=0, "FilterEMA: N<0", _state);
    ae_assert(x->cnt>=n, "FilterEMA: Length(X)<N", _state);
    ae_assert(isfinitevector(x, n, _state), "FilterEMA: X contains INF or NAN", _state);
    ae_assert(ae_fp_greater(alpha,0), "FilterEMA: Alpha<=0", _state);
    ae_assert(ae_fp_less_eq(alpha,1), "FilterEMA: Alpha>1", _state);
    
    /*
     * Quick exit, if necessary
     */
    if( n<=1||ae_fp_eq(alpha,1) )
    {
        return;
    }
    
    /*
     * Process
     */
    for(i=1; i<=n-1; i++)
    {
        x->ptr.p_double[i] = alpha*x->ptr.p_double[i]+(1-alpha)*x->ptr.p_double[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.

  -- ALGLIB --
     Copyright 25.10.2011 by Bochkanov Sergey
*************************************************************************/
void filterlrma(/* Real    */ ae_vector* x,
     ae_int_t n,
     ae_int_t k,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t i;
    ae_int_t m;
    ae_matrix xy;
    ae_vector s;
    ae_int_t info;
    double a;
    double b;
    double vara;
    double varb;
    double covab;
    double corrab;
    double p;

    ae_frame_make(_state, &_frame_block);
    ae_matrix_init(&xy, 0, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&s, 0, DT_REAL, _state, ae_true);

    ae_assert(n>=0, "FilterLRMA: N<0", _state);
    ae_assert(x->cnt>=n, "FilterLRMA: Length(X)<N", _state);
    ae_assert(isfinitevector(x, n, _state), "FilterLRMA: X contains INF or NAN", _state);
    ae_assert(k>=1, "FilterLRMA: K<1", _state);
    
    /*
     * 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 )
    {
        ae_frame_leave(_state);
        return;
    }
    
    /*
     * 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.
     */
    ae_matrix_set_length(&xy, k, 2, _state);
    ae_vector_set_length(&s, k, _state);
    for(i=0; i<=k-1; i++)
    {
        xy.ptr.pp_double[i][0] = i;
        s.ptr.p_double[i] = 1.0;
    }
    for(i=n-1; i>=2; i--)
    {
        m = ae_minint(i+1, k, _state);
        ae_v_move(&xy.ptr.pp_double[0][1], xy.stride, &x->ptr.p_double[i-m+1], 1, ae_v_len(0,m-1));
        lrlines(&xy, &s, m, &info, &a, &b, &vara, &varb, &covab, &corrab, &p, _state);
        ae_assert(info==1, "FilterLRMA: internal error", _state);
        x->ptr.p_double[i] = a+b*(m-1);
    }
    ae_frame_leave(_state);
}




/*************************************************************************
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]

  -- ALGLIB --
     Copyright 31.05.2008 by Bochkanov Sergey
*************************************************************************/
void fisherlda(/* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_int_t nvars,
     ae_int_t nclasses,
     ae_int_t* info,
     /* Real    */ ae_vector* w,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_matrix w2;

    ae_frame_make(_state, &_frame_block);
    *info = 0;
    ae_vector_clear(w);
    ae_matrix_init(&w2, 0, 0, DT_REAL, _state, ae_true);

    fisherldan(xy, npoints, nvars, nclasses, info, &w2, _state);
    if( *info>0 )
    {
        ae_vector_set_length(w, nvars-1+1, _state);
        ae_v_move(&w->ptr.p_double[0], 1, &w2.ptr.pp_double[0][0], w2.stride, ae_v_len(0,nvars-1));
    }
    ae_frame_leave(_state);
}


/*************************************************************************
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)

  -- ALGLIB --
     Copyright 31.05.2008 by Bochkanov Sergey
*************************************************************************/
void fisherldan(/* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_int_t nvars,
     ae_int_t nclasses,
     ae_int_t* info,
     /* Real    */ ae_matrix* w,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t i;
    ae_int_t j;
    ae_int_t k;
    ae_int_t m;
    double v;
    ae_vector c;
    ae_vector mu;
    ae_matrix muc;
    ae_vector nc;
    ae_matrix sw;
    ae_matrix st;
    ae_matrix z;
    ae_matrix z2;
    ae_matrix tm;
    ae_matrix sbroot;
    ae_matrix a;
    ae_matrix xyproj;
    ae_matrix wproj;
    ae_vector tf;
    ae_vector d;
    ae_vector d2;
    ae_vector work;

    ae_frame_make(_state, &_frame_block);
    *info = 0;
    ae_matrix_clear(w);
    ae_vector_init(&c, 0, DT_INT, _state, ae_true);
    ae_vector_init(&mu, 0, DT_REAL, _state, ae_true);
    ae_matrix_init(&muc, 0, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&nc, 0, DT_INT, _state, ae_true);
    ae_matrix_init(&sw, 0, 0, DT_REAL, _state, ae_true);
    ae_matrix_init(&st, 0, 0, DT_REAL, _state, ae_true);
    ae_matrix_init(&z, 0, 0, DT_REAL, _state, ae_true);
    ae_matrix_init(&z2, 0, 0, DT_REAL, _state, ae_true);
    ae_matrix_init(&tm, 0, 0, DT_REAL, _state, ae_true);
    ae_matrix_init(&sbroot, 0, 0, DT_REAL, _state, ae_true);
    ae_matrix_init(&a, 0, 0, DT_REAL, _state, ae_true);
    ae_matrix_init(&xyproj, 0, 0, DT_REAL, _state, ae_true);
    ae_matrix_init(&wproj, 0, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&tf, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&d, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&d2, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&work, 0, DT_REAL, _state, ae_true);

    
    /*
     * Test data
     */
    if( (npoints<0||nvars<1)||nclasses<2 )
    {
        *info = -1;
        ae_frame_leave(_state);
        return;
    }
    for(i=0; i<=npoints-1; i++)
    {
        if( ae_round(xy->ptr.pp_double[i][nvars], _state)<0||ae_round(xy->ptr.pp_double[i][nvars], _state)>=nclasses )
        {
            *info = -2;
            ae_frame_leave(_state);
            return;
        }
    }
    *info = 1;
    
    /*
     * Special case: NPoints<=1
     * Degenerate task.
     */
    if( npoints<=1 )
    {
        *info = 2;
        ae_matrix_set_length(w, nvars-1+1, nvars-1+1, _state);
        for(i=0; i<=nvars-1; i++)
        {
            for(j=0; j<=nvars-1; j++)
            {
                if( i==j )
                {
                    w->ptr.pp_double[i][j] = 1;
                }
                else
                {
                    w->ptr.pp_double[i][j] = 0;
                }
            }
        }
        ae_frame_leave(_state);
        return;
    }
    
    /*
     * Prepare temporaries
     */
    ae_vector_set_length(&tf, nvars-1+1, _state);
    ae_vector_set_length(&work, ae_maxint(nvars, npoints, _state)+1, _state);
    
    /*
     * Convert class labels from reals to integers (just for convenience)
     */
    ae_vector_set_length(&c, npoints-1+1, _state);
    for(i=0; i<=npoints-1; i++)
    {
        c.ptr.p_int[i] = ae_round(xy->ptr.pp_double[i][nvars], _state);
    }
    
    /*
     * Calculate class sizes and means
     */
    ae_vector_set_length(&mu, nvars-1+1, _state);
    ae_matrix_set_length(&muc, nclasses-1+1, nvars-1+1, _state);
    ae_vector_set_length(&nc, nclasses-1+1, _state);
    for(j=0; j<=nvars-1; j++)
    {
        mu.ptr.p_double[j] = 0;
    }
    for(i=0; i<=nclasses-1; i++)
    {
        nc.ptr.p_int[i] = 0;
        for(j=0; j<=nvars-1; j++)
        {
            muc.ptr.pp_double[i][j] = 0;
        }
    }
    for(i=0; i<=npoints-1; i++)
    {
        ae_v_add(&mu.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1));
        ae_v_add(&muc.ptr.pp_double[c.ptr.p_int[i]][0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1));
        nc.ptr.p_int[c.ptr.p_int[i]] = nc.ptr.p_int[c.ptr.p_int[i]]+1;
    }
    for(i=0; i<=nclasses-1; i++)
    {
        v = (double)1/(double)nc.ptr.p_int[i];
        ae_v_muld(&muc.ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1), v);
    }
    v = (double)1/(double)npoints;
    ae_v_muld(&mu.ptr.p_double[0], 1, ae_v_len(0,nvars-1), v);
    
    /*
     * Create ST matrix
     */
    ae_matrix_set_length(&st, nvars-1+1, nvars-1+1, _state);
    for(i=0; i<=nvars-1; i++)
    {
        for(j=0; j<=nvars-1; j++)
        {
            st.ptr.pp_double[i][j] = 0;
        }
    }
    for(k=0; k<=npoints-1; k++)
    {
        ae_v_move(&tf.ptr.p_double[0], 1, &xy->ptr.pp_double[k][0], 1, ae_v_len(0,nvars-1));
        ae_v_sub(&tf.ptr.p_double[0], 1, &mu.ptr.p_double[0], 1, ae_v_len(0,nvars-1));
        for(i=0; i<=nvars-1; i++)
        {
            v = tf.ptr.p_double[i];
            ae_v_addd(&st.ptr.pp_double[i][0], 1, &tf.ptr.p_double[0], 1, ae_v_len(0,nvars-1), v);
        }
    }
    
    /*
     * Create SW matrix
     */
    ae_matrix_set_length(&sw, nvars-1+1, nvars-1+1, _state);
    for(i=0; i<=nvars-1; i++)
    {
        for(j=0; j<=nvars-1; j++)
        {
            sw.ptr.pp_double[i][j] = 0;
        }
    }
    for(k=0; k<=npoints-1; k++)
    {
        ae_v_move(&tf.ptr.p_double[0], 1, &xy->ptr.pp_double[k][0], 1, ae_v_len(0,nvars-1));
        ae_v_sub(&tf.ptr.p_double[0], 1, &muc.ptr.pp_double[c.ptr.p_int[k]][0], 1, ae_v_len(0,nvars-1));
        for(i=0; i<=nvars-1; i++)
        {
            v = tf.ptr.p_double[i];
            ae_v_addd(&sw.ptr.pp_double[i][0], 1, &tf.ptr.p_double[0], 1, ae_v_len(0,nvars-1), v);
        }
    }
    
    /*
     * 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( !smatrixevd(&st, nvars, 1, ae_true, &d, &z, _state) )
    {
        *info = -4;
        ae_frame_leave(_state);
        return;
    }
    ae_matrix_set_length(w, nvars-1+1, nvars-1+1, _state);
    if( ae_fp_less_eq(d.ptr.p_double[nvars-1],0)||ae_fp_less_eq(d.ptr.p_double[0],1000*ae_machineepsilon*d.ptr.p_double[nvars-1]) )
    {
        
        /*
         * Special case: D[NVars-1]<=0
         * Degenerate task (all variables takes the same value).
         */
        if( ae_fp_less_eq(d.ptr.p_double[nvars-1],0) )
        {
            *info = 2;
            for(i=0; i<=nvars-1; i++)
            {
                for(j=0; j<=nvars-1; j++)
                {
                    if( i==j )
                    {
                        w->ptr.pp_double[i][j] = 1;
                    }
                    else
                    {
                        w->ptr.pp_double[i][j] = 0;
                    }
                }
            }
            ae_frame_leave(_state);
            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-1; k++)
        {
            if( ae_fp_less_eq(d.ptr.p_double[k],1000*ae_machineepsilon*d.ptr.p_double[nvars-1]) )
            {
                m = k+1;
            }
        }
        ae_assert(m!=0, "FisherLDAN: internal error #1", _state);
        ae_matrix_set_length(&xyproj, npoints-1+1, nvars-m+1, _state);
        matrixmatrixmultiply(xy, 0, npoints-1, 0, nvars-1, ae_false, &z, 0, nvars-1, m, nvars-1, ae_false, 1.0, &xyproj, 0, npoints-1, 0, nvars-m-1, 0.0, &work, _state);
        for(i=0; i<=npoints-1; i++)
        {
            xyproj.ptr.pp_double[i][nvars-m] = xy->ptr.pp_double[i][nvars];
        }
        fisherldan(&xyproj, npoints, nvars-m, nclasses, info, &wproj, _state);
        if( *info<0 )
        {
            ae_frame_leave(_state);
            return;
        }
        matrixmatrixmultiply(&z, 0, nvars-1, m, nvars-1, ae_false, &wproj, 0, nvars-m-1, 0, nvars-m-1, ae_false, 1.0, w, 0, nvars-1, 0, nvars-m-1, 0.0, &work, _state);
        for(k=nvars-m; k<=nvars-1; k++)
        {
            ae_v_move(&w->ptr.pp_double[0][k], w->stride, &z.ptr.pp_double[0][k-(nvars-m)], z.stride, ae_v_len(0,nvars-1));
        }
        *info = 2;
    }
    else
    {
        
        /*
         * General case: no multicollinearity
         */
        ae_matrix_set_length(&tm, nvars-1+1, nvars-1+1, _state);
        ae_matrix_set_length(&a, nvars-1+1, nvars-1+1, _state);
        matrixmatrixmultiply(&sw, 0, nvars-1, 0, nvars-1, ae_false, &z, 0, nvars-1, 0, nvars-1, ae_false, 1.0, &tm, 0, nvars-1, 0, nvars-1, 0.0, &work, _state);
        matrixmatrixmultiply(&z, 0, nvars-1, 0, nvars-1, ae_true, &tm, 0, nvars-1, 0, nvars-1, ae_false, 1.0, &a, 0, nvars-1, 0, nvars-1, 0.0, &work, _state);
        for(i=0; i<=nvars-1; i++)
        {
            for(j=0; j<=nvars-1; j++)
            {
                a.ptr.pp_double[i][j] = a.ptr.pp_double[i][j]/ae_sqrt(d.ptr.p_double[i]*d.ptr.p_double[j], _state);
            }
        }
        if( !smatrixevd(&a, nvars, 1, ae_true, &d2, &z2, _state) )
        {
            *info = -4;
            ae_frame_leave(_state);
            return;
        }
        for(k=0; k<=nvars-1; k++)
        {
            for(i=0; i<=nvars-1; i++)
            {
                tf.ptr.p_double[i] = z2.ptr.pp_double[i][k]/ae_sqrt(d.ptr.p_double[i], _state);
            }
            for(i=0; i<=nvars-1; i++)
            {
                v = ae_v_dotproduct(&z.ptr.pp_double[i][0], 1, &tf.ptr.p_double[0], 1, ae_v_len(0,nvars-1));
                w->ptr.pp_double[i][k] = v;
            }
        }
    }
    
    /*
     * Post-processing:
     * * normalization
     * * converting to non-negative form, if possible
     */
    for(k=0; k<=nvars-1; k++)
    {
        v = ae_v_dotproduct(&w->ptr.pp_double[0][k], w->stride, &w->ptr.pp_double[0][k], w->stride, ae_v_len(0,nvars-1));
        v = 1/ae_sqrt(v, _state);
        ae_v_muld(&w->ptr.pp_double[0][k], w->stride, ae_v_len(0,nvars-1), v);
        v = 0;
        for(i=0; i<=nvars-1; i++)
        {
            v = v+w->ptr.pp_double[i][k];
        }
        if( ae_fp_less(v,0) )
        {
            ae_v_muld(&w->ptr.pp_double[0][k], w->stride, ae_v_len(0,nvars-1), -1);
        }
    }
    ae_frame_leave(_state);
}




/*************************************************************************
Creates  neural  network  with  NIn  inputs,  NOut outputs, without hidden
layers, with linear output layer. Network weights are  filled  with  small
random values.

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
void mlpcreate0(ae_int_t nin,
     ae_int_t nout,
     multilayerperceptron* network,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_vector lsizes;
    ae_vector ltypes;
    ae_vector lconnfirst;
    ae_vector lconnlast;
    ae_int_t layerscount;
    ae_int_t lastproc;

    ae_frame_make(_state, &_frame_block);
    _multilayerperceptron_clear(network);
    ae_vector_init(&lsizes, 0, DT_INT, _state, ae_true);
    ae_vector_init(&ltypes, 0, DT_INT, _state, ae_true);
    ae_vector_init(&lconnfirst, 0, DT_INT, _state, ae_true);
    ae_vector_init(&lconnlast, 0, DT_INT, _state, ae_true);

    layerscount = 1+3;
    
    /*
     * Allocate arrays
     */
    ae_vector_set_length(&lsizes, layerscount-1+1, _state);
    ae_vector_set_length(&ltypes, layerscount-1+1, _state);
    ae_vector_set_length(&lconnfirst, layerscount-1+1, _state);
    ae_vector_set_length(&lconnlast, layerscount-1+1, _state);
    
    /*
     * Layers
     */
    mlpbase_addinputlayer(nin, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addbiasedsummatorlayer(nout, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addactivationlayer(-5, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    
    /*
     * Create
     */
    mlpbase_mlpcreate(nin, nout, &lsizes, &ltypes, &lconnfirst, &lconnlast, layerscount, ae_false, network, _state);
    mlpbase_fillhighlevelinformation(network, nin, 0, 0, nout, ae_false, ae_true, _state);
    ae_frame_leave(_state);
}


/*************************************************************************
Same  as  MLPCreate0,  but  with  one  hidden  layer  (NHid  neurons) with
non-linear activation function. Output layer is linear.

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
void mlpcreate1(ae_int_t nin,
     ae_int_t nhid,
     ae_int_t nout,
     multilayerperceptron* network,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_vector lsizes;
    ae_vector ltypes;
    ae_vector lconnfirst;
    ae_vector lconnlast;
    ae_int_t layerscount;
    ae_int_t lastproc;

    ae_frame_make(_state, &_frame_block);
    _multilayerperceptron_clear(network);
    ae_vector_init(&lsizes, 0, DT_INT, _state, ae_true);
    ae_vector_init(&ltypes, 0, DT_INT, _state, ae_true);
    ae_vector_init(&lconnfirst, 0, DT_INT, _state, ae_true);
    ae_vector_init(&lconnlast, 0, DT_INT, _state, ae_true);

    layerscount = 1+3+3;
    
    /*
     * Allocate arrays
     */
    ae_vector_set_length(&lsizes, layerscount-1+1, _state);
    ae_vector_set_length(&ltypes, layerscount-1+1, _state);
    ae_vector_set_length(&lconnfirst, layerscount-1+1, _state);
    ae_vector_set_length(&lconnlast, layerscount-1+1, _state);
    
    /*
     * Layers
     */
    mlpbase_addinputlayer(nin, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addbiasedsummatorlayer(nhid, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addactivationlayer(1, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addbiasedsummatorlayer(nout, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addactivationlayer(-5, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    
    /*
     * Create
     */
    mlpbase_mlpcreate(nin, nout, &lsizes, &ltypes, &lconnfirst, &lconnlast, layerscount, ae_false, network, _state);
    mlpbase_fillhighlevelinformation(network, nin, nhid, 0, nout, ae_false, ae_true, _state);
    ae_frame_leave(_state);
}


/*************************************************************************
Same as MLPCreate0, but with two hidden layers (NHid1 and  NHid2  neurons)
with non-linear activation function. Output layer is linear.
 $ALL

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
void mlpcreate2(ae_int_t nin,
     ae_int_t nhid1,
     ae_int_t nhid2,
     ae_int_t nout,
     multilayerperceptron* network,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_vector lsizes;
    ae_vector ltypes;
    ae_vector lconnfirst;
    ae_vector lconnlast;
    ae_int_t layerscount;
    ae_int_t lastproc;

    ae_frame_make(_state, &_frame_block);
    _multilayerperceptron_clear(network);
    ae_vector_init(&lsizes, 0, DT_INT, _state, ae_true);
    ae_vector_init(&ltypes, 0, DT_INT, _state, ae_true);
    ae_vector_init(&lconnfirst, 0, DT_INT, _state, ae_true);
    ae_vector_init(&lconnlast, 0, DT_INT, _state, ae_true);

    layerscount = 1+3+3+3;
    
    /*
     * Allocate arrays
     */
    ae_vector_set_length(&lsizes, layerscount-1+1, _state);
    ae_vector_set_length(&ltypes, layerscount-1+1, _state);
    ae_vector_set_length(&lconnfirst, layerscount-1+1, _state);
    ae_vector_set_length(&lconnlast, layerscount-1+1, _state);
    
    /*
     * Layers
     */
    mlpbase_addinputlayer(nin, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addbiasedsummatorlayer(nhid1, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addactivationlayer(1, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addbiasedsummatorlayer(nhid2, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addactivationlayer(1, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addbiasedsummatorlayer(nout, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addactivationlayer(-5, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    
    /*
     * Create
     */
    mlpbase_mlpcreate(nin, nout, &lsizes, &ltypes, &lconnfirst, &lconnlast, layerscount, ae_false, network, _state);
    mlpbase_fillhighlevelinformation(network, nin, nhid1, nhid2, nout, ae_false, ae_true, _state);
    ae_frame_leave(_state);
}


/*************************************************************************
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.


  -- ALGLIB --
     Copyright 30.03.2008 by Bochkanov Sergey
*************************************************************************/
void mlpcreateb0(ae_int_t nin,
     ae_int_t nout,
     double b,
     double d,
     multilayerperceptron* network,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_vector lsizes;
    ae_vector ltypes;
    ae_vector lconnfirst;
    ae_vector lconnlast;
    ae_int_t layerscount;
    ae_int_t lastproc;
    ae_int_t i;

    ae_frame_make(_state, &_frame_block);
    _multilayerperceptron_clear(network);
    ae_vector_init(&lsizes, 0, DT_INT, _state, ae_true);
    ae_vector_init(&ltypes, 0, DT_INT, _state, ae_true);
    ae_vector_init(&lconnfirst, 0, DT_INT, _state, ae_true);
    ae_vector_init(&lconnlast, 0, DT_INT, _state, ae_true);

    layerscount = 1+3;
    if( ae_fp_greater_eq(d,0) )
    {
        d = 1;
    }
    else
    {
        d = -1;
    }
    
    /*
     * Allocate arrays
     */
    ae_vector_set_length(&lsizes, layerscount-1+1, _state);
    ae_vector_set_length(&ltypes, layerscount-1+1, _state);
    ae_vector_set_length(&lconnfirst, layerscount-1+1, _state);
    ae_vector_set_length(&lconnlast, layerscount-1+1, _state);
    
    /*
     * Layers
     */
    mlpbase_addinputlayer(nin, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addbiasedsummatorlayer(nout, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addactivationlayer(3, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    
    /*
     * Create
     */
    mlpbase_mlpcreate(nin, nout, &lsizes, &ltypes, &lconnfirst, &lconnlast, layerscount, ae_false, network, _state);
    mlpbase_fillhighlevelinformation(network, nin, 0, 0, nout, ae_false, ae_false, _state);
    
    /*
     * Turn on ouputs shift/scaling.
     */
    for(i=nin; i<=nin+nout-1; i++)
    {
        network->columnmeans.ptr.p_double[i] = b;
        network->columnsigmas.ptr.p_double[i] = d;
    }
    ae_frame_leave(_state);
}


/*************************************************************************
Same as MLPCreateB0 but with non-linear hidden layer.

  -- ALGLIB --
     Copyright 30.03.2008 by Bochkanov Sergey
*************************************************************************/
void mlpcreateb1(ae_int_t nin,
     ae_int_t nhid,
     ae_int_t nout,
     double b,
     double d,
     multilayerperceptron* network,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_vector lsizes;
    ae_vector ltypes;
    ae_vector lconnfirst;
    ae_vector lconnlast;
    ae_int_t layerscount;
    ae_int_t lastproc;
    ae_int_t i;

    ae_frame_make(_state, &_frame_block);
    _multilayerperceptron_clear(network);
    ae_vector_init(&lsizes, 0, DT_INT, _state, ae_true);
    ae_vector_init(&ltypes, 0, DT_INT, _state, ae_true);
    ae_vector_init(&lconnfirst, 0, DT_INT, _state, ae_true);
    ae_vector_init(&lconnlast, 0, DT_INT, _state, ae_true);

    layerscount = 1+3+3;
    if( ae_fp_greater_eq(d,0) )
    {
        d = 1;
    }
    else
    {
        d = -1;
    }
    
    /*
     * Allocate arrays
     */
    ae_vector_set_length(&lsizes, layerscount-1+1, _state);
    ae_vector_set_length(&ltypes, layerscount-1+1, _state);
    ae_vector_set_length(&lconnfirst, layerscount-1+1, _state);
    ae_vector_set_length(&lconnlast, layerscount-1+1, _state);
    
    /*
     * Layers
     */
    mlpbase_addinputlayer(nin, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addbiasedsummatorlayer(nhid, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addactivationlayer(1, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addbiasedsummatorlayer(nout, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addactivationlayer(3, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    
    /*
     * Create
     */
    mlpbase_mlpcreate(nin, nout, &lsizes, &ltypes, &lconnfirst, &lconnlast, layerscount, ae_false, network, _state);
    mlpbase_fillhighlevelinformation(network, nin, nhid, 0, nout, ae_false, ae_false, _state);
    
    /*
     * Turn on ouputs shift/scaling.
     */
    for(i=nin; i<=nin+nout-1; i++)
    {
        network->columnmeans.ptr.p_double[i] = b;
        network->columnsigmas.ptr.p_double[i] = d;
    }
    ae_frame_leave(_state);
}


/*************************************************************************
Same as MLPCreateB0 but with two non-linear hidden layers.

  -- ALGLIB --
     Copyright 30.03.2008 by Bochkanov Sergey
*************************************************************************/
void mlpcreateb2(ae_int_t nin,
     ae_int_t nhid1,
     ae_int_t nhid2,
     ae_int_t nout,
     double b,
     double d,
     multilayerperceptron* network,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_vector lsizes;
    ae_vector ltypes;
    ae_vector lconnfirst;
    ae_vector lconnlast;
    ae_int_t layerscount;
    ae_int_t lastproc;
    ae_int_t i;

    ae_frame_make(_state, &_frame_block);
    _multilayerperceptron_clear(network);
    ae_vector_init(&lsizes, 0, DT_INT, _state, ae_true);
    ae_vector_init(&ltypes, 0, DT_INT, _state, ae_true);
    ae_vector_init(&lconnfirst, 0, DT_INT, _state, ae_true);
    ae_vector_init(&lconnlast, 0, DT_INT, _state, ae_true);

    layerscount = 1+3+3+3;
    if( ae_fp_greater_eq(d,0) )
    {
        d = 1;
    }
    else
    {
        d = -1;
    }
    
    /*
     * Allocate arrays
     */
    ae_vector_set_length(&lsizes, layerscount-1+1, _state);
    ae_vector_set_length(&ltypes, layerscount-1+1, _state);
    ae_vector_set_length(&lconnfirst, layerscount-1+1, _state);
    ae_vector_set_length(&lconnlast, layerscount-1+1, _state);
    
    /*
     * Layers
     */
    mlpbase_addinputlayer(nin, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addbiasedsummatorlayer(nhid1, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addactivationlayer(1, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addbiasedsummatorlayer(nhid2, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addactivationlayer(1, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addbiasedsummatorlayer(nout, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addactivationlayer(3, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    
    /*
     * Create
     */
    mlpbase_mlpcreate(nin, nout, &lsizes, &ltypes, &lconnfirst, &lconnlast, layerscount, ae_false, network, _state);
    mlpbase_fillhighlevelinformation(network, nin, nhid1, nhid2, nout, ae_false, ae_false, _state);
    
    /*
     * Turn on ouputs shift/scaling.
     */
    for(i=nin; i<=nin+nout-1; i++)
    {
        network->columnmeans.ptr.p_double[i] = b;
        network->columnsigmas.ptr.p_double[i] = d;
    }
    ae_frame_leave(_state);
}


/*************************************************************************
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].

  -- ALGLIB --
     Copyright 30.03.2008 by Bochkanov Sergey
*************************************************************************/
void mlpcreater0(ae_int_t nin,
     ae_int_t nout,
     double a,
     double b,
     multilayerperceptron* network,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_vector lsizes;
    ae_vector ltypes;
    ae_vector lconnfirst;
    ae_vector lconnlast;
    ae_int_t layerscount;
    ae_int_t lastproc;
    ae_int_t i;

    ae_frame_make(_state, &_frame_block);
    _multilayerperceptron_clear(network);
    ae_vector_init(&lsizes, 0, DT_INT, _state, ae_true);
    ae_vector_init(&ltypes, 0, DT_INT, _state, ae_true);
    ae_vector_init(&lconnfirst, 0, DT_INT, _state, ae_true);
    ae_vector_init(&lconnlast, 0, DT_INT, _state, ae_true);

    layerscount = 1+3;
    
    /*
     * Allocate arrays
     */
    ae_vector_set_length(&lsizes, layerscount-1+1, _state);
    ae_vector_set_length(&ltypes, layerscount-1+1, _state);
    ae_vector_set_length(&lconnfirst, layerscount-1+1, _state);
    ae_vector_set_length(&lconnlast, layerscount-1+1, _state);
    
    /*
     * Layers
     */
    mlpbase_addinputlayer(nin, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addbiasedsummatorlayer(nout, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addactivationlayer(1, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    
    /*
     * Create
     */
    mlpbase_mlpcreate(nin, nout, &lsizes, &ltypes, &lconnfirst, &lconnlast, layerscount, ae_false, network, _state);
    mlpbase_fillhighlevelinformation(network, nin, 0, 0, nout, ae_false, ae_false, _state);
    
    /*
     * Turn on outputs shift/scaling.
     */
    for(i=nin; i<=nin+nout-1; i++)
    {
        network->columnmeans.ptr.p_double[i] = 0.5*(a+b);
        network->columnsigmas.ptr.p_double[i] = 0.5*(a-b);
    }
    ae_frame_leave(_state);
}


/*************************************************************************
Same as MLPCreateR0, but with non-linear hidden layer.

  -- ALGLIB --
     Copyright 30.03.2008 by Bochkanov Sergey
*************************************************************************/
void mlpcreater1(ae_int_t nin,
     ae_int_t nhid,
     ae_int_t nout,
     double a,
     double b,
     multilayerperceptron* network,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_vector lsizes;
    ae_vector ltypes;
    ae_vector lconnfirst;
    ae_vector lconnlast;
    ae_int_t layerscount;
    ae_int_t lastproc;
    ae_int_t i;

    ae_frame_make(_state, &_frame_block);
    _multilayerperceptron_clear(network);
    ae_vector_init(&lsizes, 0, DT_INT, _state, ae_true);
    ae_vector_init(&ltypes, 0, DT_INT, _state, ae_true);
    ae_vector_init(&lconnfirst, 0, DT_INT, _state, ae_true);
    ae_vector_init(&lconnlast, 0, DT_INT, _state, ae_true);

    layerscount = 1+3+3;
    
    /*
     * Allocate arrays
     */
    ae_vector_set_length(&lsizes, layerscount-1+1, _state);
    ae_vector_set_length(&ltypes, layerscount-1+1, _state);
    ae_vector_set_length(&lconnfirst, layerscount-1+1, _state);
    ae_vector_set_length(&lconnlast, layerscount-1+1, _state);
    
    /*
     * Layers
     */
    mlpbase_addinputlayer(nin, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addbiasedsummatorlayer(nhid, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addactivationlayer(1, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addbiasedsummatorlayer(nout, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addactivationlayer(1, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    
    /*
     * Create
     */
    mlpbase_mlpcreate(nin, nout, &lsizes, &ltypes, &lconnfirst, &lconnlast, layerscount, ae_false, network, _state);
    mlpbase_fillhighlevelinformation(network, nin, nhid, 0, nout, ae_false, ae_false, _state);
    
    /*
     * Turn on outputs shift/scaling.
     */
    for(i=nin; i<=nin+nout-1; i++)
    {
        network->columnmeans.ptr.p_double[i] = 0.5*(a+b);
        network->columnsigmas.ptr.p_double[i] = 0.5*(a-b);
    }
    ae_frame_leave(_state);
}


/*************************************************************************
Same as MLPCreateR0, but with two non-linear hidden layers.

  -- ALGLIB --
     Copyright 30.03.2008 by Bochkanov Sergey
*************************************************************************/
void mlpcreater2(ae_int_t nin,
     ae_int_t nhid1,
     ae_int_t nhid2,
     ae_int_t nout,
     double a,
     double b,
     multilayerperceptron* network,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_vector lsizes;
    ae_vector ltypes;
    ae_vector lconnfirst;
    ae_vector lconnlast;
    ae_int_t layerscount;
    ae_int_t lastproc;
    ae_int_t i;

    ae_frame_make(_state, &_frame_block);
    _multilayerperceptron_clear(network);
    ae_vector_init(&lsizes, 0, DT_INT, _state, ae_true);
    ae_vector_init(&ltypes, 0, DT_INT, _state, ae_true);
    ae_vector_init(&lconnfirst, 0, DT_INT, _state, ae_true);
    ae_vector_init(&lconnlast, 0, DT_INT, _state, ae_true);

    layerscount = 1+3+3+3;
    
    /*
     * Allocate arrays
     */
    ae_vector_set_length(&lsizes, layerscount-1+1, _state);
    ae_vector_set_length(&ltypes, layerscount-1+1, _state);
    ae_vector_set_length(&lconnfirst, layerscount-1+1, _state);
    ae_vector_set_length(&lconnlast, layerscount-1+1, _state);
    
    /*
     * Layers
     */
    mlpbase_addinputlayer(nin, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addbiasedsummatorlayer(nhid1, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addactivationlayer(1, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addbiasedsummatorlayer(nhid2, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addactivationlayer(1, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addbiasedsummatorlayer(nout, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addactivationlayer(1, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    
    /*
     * Create
     */
    mlpbase_mlpcreate(nin, nout, &lsizes, &ltypes, &lconnfirst, &lconnlast, layerscount, ae_false, network, _state);
    mlpbase_fillhighlevelinformation(network, nin, nhid1, nhid2, nout, ae_false, ae_false, _state);
    
    /*
     * Turn on outputs shift/scaling.
     */
    for(i=nin; i<=nin+nout-1; i++)
    {
        network->columnmeans.ptr.p_double[i] = 0.5*(a+b);
        network->columnsigmas.ptr.p_double[i] = 0.5*(a-b);
    }
    ae_frame_leave(_state);
}


/*************************************************************************
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).

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
void mlpcreatec0(ae_int_t nin,
     ae_int_t nout,
     multilayerperceptron* network,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_vector lsizes;
    ae_vector ltypes;
    ae_vector lconnfirst;
    ae_vector lconnlast;
    ae_int_t layerscount;
    ae_int_t lastproc;

    ae_frame_make(_state, &_frame_block);
    _multilayerperceptron_clear(network);
    ae_vector_init(&lsizes, 0, DT_INT, _state, ae_true);
    ae_vector_init(&ltypes, 0, DT_INT, _state, ae_true);
    ae_vector_init(&lconnfirst, 0, DT_INT, _state, ae_true);
    ae_vector_init(&lconnlast, 0, DT_INT, _state, ae_true);

    ae_assert(nout>=2, "MLPCreateC0: NOut<2!", _state);
    layerscount = 1+2+1;
    
    /*
     * Allocate arrays
     */
    ae_vector_set_length(&lsizes, layerscount-1+1, _state);
    ae_vector_set_length(&ltypes, layerscount-1+1, _state);
    ae_vector_set_length(&lconnfirst, layerscount-1+1, _state);
    ae_vector_set_length(&lconnlast, layerscount-1+1, _state);
    
    /*
     * Layers
     */
    mlpbase_addinputlayer(nin, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addbiasedsummatorlayer(nout-1, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addzerolayer(&lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    
    /*
     * Create
     */
    mlpbase_mlpcreate(nin, nout, &lsizes, &ltypes, &lconnfirst, &lconnlast, layerscount, ae_true, network, _state);
    mlpbase_fillhighlevelinformation(network, nin, 0, 0, nout, ae_true, ae_true, _state);
    ae_frame_leave(_state);
}


/*************************************************************************
Same as MLPCreateC0, but with one non-linear hidden layer.

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
void mlpcreatec1(ae_int_t nin,
     ae_int_t nhid,
     ae_int_t nout,
     multilayerperceptron* network,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_vector lsizes;
    ae_vector ltypes;
    ae_vector lconnfirst;
    ae_vector lconnlast;
    ae_int_t layerscount;
    ae_int_t lastproc;

    ae_frame_make(_state, &_frame_block);
    _multilayerperceptron_clear(network);
    ae_vector_init(&lsizes, 0, DT_INT, _state, ae_true);
    ae_vector_init(&ltypes, 0, DT_INT, _state, ae_true);
    ae_vector_init(&lconnfirst, 0, DT_INT, _state, ae_true);
    ae_vector_init(&lconnlast, 0, DT_INT, _state, ae_true);

    ae_assert(nout>=2, "MLPCreateC1: NOut<2!", _state);
    layerscount = 1+3+2+1;
    
    /*
     * Allocate arrays
     */
    ae_vector_set_length(&lsizes, layerscount-1+1, _state);
    ae_vector_set_length(&ltypes, layerscount-1+1, _state);
    ae_vector_set_length(&lconnfirst, layerscount-1+1, _state);
    ae_vector_set_length(&lconnlast, layerscount-1+1, _state);
    
    /*
     * Layers
     */
    mlpbase_addinputlayer(nin, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addbiasedsummatorlayer(nhid, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addactivationlayer(1, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addbiasedsummatorlayer(nout-1, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addzerolayer(&lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    
    /*
     * Create
     */
    mlpbase_mlpcreate(nin, nout, &lsizes, &ltypes, &lconnfirst, &lconnlast, layerscount, ae_true, network, _state);
    mlpbase_fillhighlevelinformation(network, nin, nhid, 0, nout, ae_true, ae_true, _state);
    ae_frame_leave(_state);
}


/*************************************************************************
Same as MLPCreateC0, but with two non-linear hidden layers.

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
void mlpcreatec2(ae_int_t nin,
     ae_int_t nhid1,
     ae_int_t nhid2,
     ae_int_t nout,
     multilayerperceptron* network,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_vector lsizes;
    ae_vector ltypes;
    ae_vector lconnfirst;
    ae_vector lconnlast;
    ae_int_t layerscount;
    ae_int_t lastproc;

    ae_frame_make(_state, &_frame_block);
    _multilayerperceptron_clear(network);
    ae_vector_init(&lsizes, 0, DT_INT, _state, ae_true);
    ae_vector_init(&ltypes, 0, DT_INT, _state, ae_true);
    ae_vector_init(&lconnfirst, 0, DT_INT, _state, ae_true);
    ae_vector_init(&lconnlast, 0, DT_INT, _state, ae_true);

    ae_assert(nout>=2, "MLPCreateC2: NOut<2!", _state);
    layerscount = 1+3+3+2+1;
    
    /*
     * Allocate arrays
     */
    ae_vector_set_length(&lsizes, layerscount-1+1, _state);
    ae_vector_set_length(&ltypes, layerscount-1+1, _state);
    ae_vector_set_length(&lconnfirst, layerscount-1+1, _state);
    ae_vector_set_length(&lconnlast, layerscount-1+1, _state);
    
    /*
     * Layers
     */
    mlpbase_addinputlayer(nin, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addbiasedsummatorlayer(nhid1, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addactivationlayer(1, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addbiasedsummatorlayer(nhid2, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addactivationlayer(1, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addbiasedsummatorlayer(nout-1, &lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    mlpbase_addzerolayer(&lsizes, &ltypes, &lconnfirst, &lconnlast, &lastproc, _state);
    
    /*
     * Create
     */
    mlpbase_mlpcreate(nin, nout, &lsizes, &ltypes, &lconnfirst, &lconnlast, layerscount, ae_true, network, _state);
    mlpbase_fillhighlevelinformation(network, nin, nhid1, nhid2, nout, ae_true, ae_true, _state);
    ae_frame_leave(_state);
}


/*************************************************************************
Copying of neural network

INPUT PARAMETERS:
    Network1 -   original

OUTPUT PARAMETERS:
    Network2 -   copy

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
void mlpcopy(multilayerperceptron* network1,
     multilayerperceptron* network2,
     ae_state *_state)
{

    _multilayerperceptron_clear(network2);

    network2->hlnetworktype = network1->hlnetworktype;
    network2->hlnormtype = network1->hlnormtype;
    copyintegerarray(&network1->hllayersizes, &network2->hllayersizes, _state);
    copyintegerarray(&network1->hlconnections, &network2->hlconnections, _state);
    copyintegerarray(&network1->hlneurons, &network2->hlneurons, _state);
    copyintegerarray(&network1->structinfo, &network2->structinfo, _state);
    copyrealarray(&network1->weights, &network2->weights, _state);
    copyrealarray(&network1->columnmeans, &network2->columnmeans, _state);
    copyrealarray(&network1->columnsigmas, &network2->columnsigmas, _state);
    copyrealarray(&network1->neurons, &network2->neurons, _state);
    copyrealarray(&network1->dfdnet, &network2->dfdnet, _state);
    copyrealarray(&network1->derror, &network2->derror, _state);
    copyrealarray(&network1->x, &network2->x, _state);
    copyrealarray(&network1->y, &network2->y, _state);
    copyrealmatrix(&network1->chunks, &network2->chunks, _state);
    copyrealarray(&network1->nwbuf, &network2->nwbuf, _state);
    copyintegerarray(&network1->integerbuf, &network2->integerbuf, _state);
}


/*************************************************************************
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

  -- ALGLIB --
     Copyright 29.03.2008 by Bochkanov Sergey
*************************************************************************/
void mlpserializeold(multilayerperceptron* network,
     /* Real    */ ae_vector* ra,
     ae_int_t* rlen,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t ssize;
    ae_int_t ntotal;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    ae_int_t sigmalen;
    ae_int_t offs;

    ae_vector_clear(ra);
    *rlen = 0;

    
    /*
     * Unload info
     */
    ssize = network->structinfo.ptr.p_int[0];
    nin = network->structinfo.ptr.p_int[1];
    nout = network->structinfo.ptr.p_int[2];
    ntotal = network->structinfo.ptr.p_int[3];
    wcount = network->structinfo.ptr.p_int[4];
    if( mlpissoftmax(network, _state) )
    {
        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;
    ae_vector_set_length(ra, *rlen-1+1, _state);
    ra->ptr.p_double[0] = *rlen;
    ra->ptr.p_double[1] = mlpbase_mlpvnum;
    ra->ptr.p_double[2] = ssize;
    offs = 3;
    for(i=0; i<=ssize-1; i++)
    {
        ra->ptr.p_double[offs+i] = network->structinfo.ptr.p_int[i];
    }
    offs = offs+ssize;
    ae_v_move(&ra->ptr.p_double[offs], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(offs,offs+wcount-1));
    offs = offs+wcount;
    ae_v_move(&ra->ptr.p_double[offs], 1, &network->columnmeans.ptr.p_double[0], 1, ae_v_len(offs,offs+sigmalen-1));
    offs = offs+sigmalen;
    ae_v_move(&ra->ptr.p_double[offs], 1, &network->columnsigmas.ptr.p_double[0], 1, ae_v_len(offs,offs+sigmalen-1));
    offs = offs+sigmalen;
}


/*************************************************************************
Unserialization of MultiLayerPerceptron strucure

INPUT PARAMETERS:
    RA      -   real array which stores network

OUTPUT PARAMETERS:
    Network -   restored network

  -- ALGLIB --
     Copyright 29.03.2008 by Bochkanov Sergey
*************************************************************************/
void mlpunserializeold(/* Real    */ ae_vector* ra,
     multilayerperceptron* network,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t ssize;
    ae_int_t ntotal;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    ae_int_t sigmalen;
    ae_int_t offs;

    _multilayerperceptron_clear(network);

    ae_assert(ae_round(ra->ptr.p_double[1], _state)==mlpbase_mlpvnum, "MLPUnserialize: incorrect array!", _state);
    
    /*
     * Unload StructInfo from IA
     */
    offs = 3;
    ssize = ae_round(ra->ptr.p_double[2], _state);
    ae_vector_set_length(&network->structinfo, ssize-1+1, _state);
    for(i=0; i<=ssize-1; i++)
    {
        network->structinfo.ptr.p_int[i] = ae_round(ra->ptr.p_double[offs+i], _state);
    }
    offs = offs+ssize;
    
    /*
     * Unload info from StructInfo
     */
    ssize = network->structinfo.ptr.p_int[0];
    nin = network->structinfo.ptr.p_int[1];
    nout = network->structinfo.ptr.p_int[2];
    ntotal = network->structinfo.ptr.p_int[3];
    wcount = network->structinfo.ptr.p_int[4];
    if( network->structinfo.ptr.p_int[6]==0 )
    {
        sigmalen = nin+nout;
    }
    else
    {
        sigmalen = nin;
    }
    
    /*
     * Allocate space for other fields
     */
    ae_vector_set_length(&network->weights, wcount-1+1, _state);
    ae_vector_set_length(&network->columnmeans, sigmalen-1+1, _state);
    ae_vector_set_length(&network->columnsigmas, sigmalen-1+1, _state);
    ae_vector_set_length(&network->neurons, ntotal-1+1, _state);
    ae_matrix_set_length(&network->chunks, 3*ntotal+1, mlpbase_chunksize-1+1, _state);
    ae_vector_set_length(&network->nwbuf, ae_maxint(wcount, 2*nout, _state)-1+1, _state);
    ae_vector_set_length(&network->dfdnet, ntotal-1+1, _state);
    ae_vector_set_length(&network->x, nin-1+1, _state);
    ae_vector_set_length(&network->y, nout-1+1, _state);
    ae_vector_set_length(&network->derror, ntotal-1+1, _state);
    
    /*
     * Copy parameters from RA
     */
    ae_v_move(&network->weights.ptr.p_double[0], 1, &ra->ptr.p_double[offs], 1, ae_v_len(0,wcount-1));
    offs = offs+wcount;
    ae_v_move(&network->columnmeans.ptr.p_double[0], 1, &ra->ptr.p_double[offs], 1, ae_v_len(0,sigmalen-1));
    offs = offs+sigmalen;
    ae_v_move(&network->columnsigmas.ptr.p_double[0], 1, &ra->ptr.p_double[offs], 1, ae_v_len(0,sigmalen-1));
    offs = offs+sigmalen;
}


/*************************************************************************
Randomization of neural network weights

  -- ALGLIB --
     Copyright 06.11.2007 by Bochkanov Sergey
*************************************************************************/
void mlprandomize(multilayerperceptron* network, ae_state *_state)
{
    ae_int_t i;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;


    mlpproperties(network, &nin, &nout, &wcount, _state);
    for(i=0; i<=wcount-1; i++)
    {
        network->weights.ptr.p_double[i] = ae_randomreal(_state)-0.5;
    }
}


/*************************************************************************
Randomization of neural network weights and standartisator

  -- ALGLIB --
     Copyright 10.03.2008 by Bochkanov Sergey
*************************************************************************/
void mlprandomizefull(multilayerperceptron* network, ae_state *_state)
{
    ae_int_t i;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    ae_int_t ntotal;
    ae_int_t istart;
    ae_int_t offs;
    ae_int_t ntype;


    mlpproperties(network, &nin, &nout, &wcount, _state);
    ntotal = network->structinfo.ptr.p_int[3];
    istart = network->structinfo.ptr.p_int[5];
    
    /*
     * Process network
     */
    for(i=0; i<=wcount-1; i++)
    {
        network->weights.ptr.p_double[i] = ae_randomreal(_state)-0.5;
    }
    for(i=0; i<=nin-1; i++)
    {
        network->columnmeans.ptr.p_double[i] = 2*ae_randomreal(_state)-1;
        network->columnsigmas.ptr.p_double[i] = 1.5*ae_randomreal(_state)+0.5;
    }
    if( !mlpissoftmax(network, _state) )
    {
        for(i=0; i<=nout-1; i++)
        {
            offs = istart+(ntotal-nout+i)*mlpbase_nfieldwidth;
            ntype = network->structinfo.ptr.p_int[offs+0];
            if( ntype==0 )
            {
                
                /*
                 * Shifts are changed only for linear outputs neurons
                 */
                network->columnmeans.ptr.p_double[nin+i] = 2*ae_randomreal(_state)-1;
            }
            if( ntype==0||ntype==3 )
            {
                
                /*
                 * Scales are changed only for linear or bounded outputs neurons.
                 * Note that scale randomization preserves sign.
                 */
                network->columnsigmas.ptr.p_double[nin+i] = ae_sign(network->columnsigmas.ptr.p_double[nin+i], _state)*(1.5*ae_randomreal(_state)+0.5);
            }
        }
    }
}


/*************************************************************************
Internal subroutine.

  -- ALGLIB --
     Copyright 30.03.2008 by Bochkanov Sergey
*************************************************************************/
void mlpinitpreprocessor(multilayerperceptron* network,
     /* Real    */ ae_matrix* xy,
     ae_int_t ssize,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t i;
    ae_int_t j;
    ae_int_t jmax;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    ae_int_t ntotal;
    ae_int_t istart;
    ae_int_t offs;
    ae_int_t ntype;
    ae_vector means;
    ae_vector sigmas;
    double s;

    ae_frame_make(_state, &_frame_block);
    ae_vector_init(&means, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&sigmas, 0, DT_REAL, _state, ae_true);

    mlpproperties(network, &nin, &nout, &wcount, _state);
    ntotal = network->structinfo.ptr.p_int[3];
    istart = network->structinfo.ptr.p_int[5];
    
    /*
     * Means/Sigmas
     */
    if( mlpissoftmax(network, _state) )
    {
        jmax = nin-1;
    }
    else
    {
        jmax = nin+nout-1;
    }
    ae_vector_set_length(&means, jmax+1, _state);
    ae_vector_set_length(&sigmas, jmax+1, _state);
    for(i=0; i<=jmax; i++)
    {
        means.ptr.p_double[i] = 0;
        sigmas.ptr.p_double[i] = 0;
    }
    for(i=0; i<=ssize-1; i++)
    {
        for(j=0; j<=jmax; j++)
        {
            means.ptr.p_double[j] = means.ptr.p_double[j]+xy->ptr.pp_double[i][j];
        }
    }
    for(i=0; i<=jmax; i++)
    {
        means.ptr.p_double[i] = means.ptr.p_double[i]/ssize;
    }
    for(i=0; i<=ssize-1; i++)
    {
        for(j=0; j<=jmax; j++)
        {
            sigmas.ptr.p_double[j] = sigmas.ptr.p_double[j]+ae_sqr(xy->ptr.pp_double[i][j]-means.ptr.p_double[j], _state);
        }
    }
    for(i=0; i<=jmax; i++)
    {
        sigmas.ptr.p_double[i] = ae_sqrt(sigmas.ptr.p_double[i]/ssize, _state);
    }
    
    /*
     * Inputs
     */
    for(i=0; i<=nin-1; i++)
    {
        network->columnmeans.ptr.p_double[i] = means.ptr.p_double[i];
        network->columnsigmas.ptr.p_double[i] = sigmas.ptr.p_double[i];
        if( ae_fp_eq(network->columnsigmas.ptr.p_double[i],0) )
        {
            network->columnsigmas.ptr.p_double[i] = 1;
        }
    }
    
    /*
     * Outputs
     */
    if( !mlpissoftmax(network, _state) )
    {
        for(i=0; i<=nout-1; i++)
        {
            offs = istart+(ntotal-nout+i)*mlpbase_nfieldwidth;
            ntype = network->structinfo.ptr.p_int[offs+0];
            
            /*
             * Linear outputs
             */
            if( ntype==0 )
            {
                network->columnmeans.ptr.p_double[nin+i] = means.ptr.p_double[nin+i];
                network->columnsigmas.ptr.p_double[nin+i] = sigmas.ptr.p_double[nin+i];
                if( ae_fp_eq(network->columnsigmas.ptr.p_double[nin+i],0) )
                {
                    network->columnsigmas.ptr.p_double[nin+i] = 1;
                }
            }
            
            /*
             * Bounded outputs (half-interval)
             */
            if( ntype==3 )
            {
                s = means.ptr.p_double[nin+i]-network->columnmeans.ptr.p_double[nin+i];
                if( ae_fp_eq(s,0) )
                {
                    s = ae_sign(network->columnsigmas.ptr.p_double[nin+i], _state);
                }
                if( ae_fp_eq(s,0) )
                {
                    s = 1.0;
                }
                network->columnsigmas.ptr.p_double[nin+i] = ae_sign(network->columnsigmas.ptr.p_double[nin+i], _state)*ae_fabs(s, _state);
                if( ae_fp_eq(network->columnsigmas.ptr.p_double[nin+i],0) )
                {
                    network->columnsigmas.ptr.p_double[nin+i] = 1;
                }
            }
        }
    }
    ae_frame_leave(_state);
}


/*************************************************************************
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.

  -- ALGLIB --
     Copyright 26.07.2012 by Bochkanov Sergey
*************************************************************************/
void mlpinitpreprocessorsparse(multilayerperceptron* network,
     sparsematrix* xy,
     ae_int_t ssize,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t jmax;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    ae_int_t ntotal;
    ae_int_t istart;
    ae_int_t offs;
    ae_int_t ntype;
    ae_vector means;
    ae_vector sigmas;
    double s;
    ae_int_t i;
    ae_int_t j;

    ae_frame_make(_state, &_frame_block);
    ae_vector_init(&means, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&sigmas, 0, DT_REAL, _state, ae_true);

    mlpproperties(network, &nin, &nout, &wcount, _state);
    ntotal = network->structinfo.ptr.p_int[3];
    istart = network->structinfo.ptr.p_int[5];
    
    /*
     * Means/Sigmas
     */
    if( mlpissoftmax(network, _state) )
    {
        jmax = nin-1;
    }
    else
    {
        jmax = nin+nout-1;
    }
    ae_vector_set_length(&means, jmax+1, _state);
    ae_vector_set_length(&sigmas, jmax+1, _state);
    for(i=0; i<=jmax; i++)
    {
        means.ptr.p_double[i] = 0;
        sigmas.ptr.p_double[i] = 0;
    }
    for(i=0; i<=ssize-1; i++)
    {
        sparsegetrow(xy, i, &network->xyrow, _state);
        for(j=0; j<=jmax; j++)
        {
            means.ptr.p_double[j] = means.ptr.p_double[j]+network->xyrow.ptr.p_double[j];
        }
    }
    for(i=0; i<=jmax; i++)
    {
        means.ptr.p_double[i] = means.ptr.p_double[i]/ssize;
    }
    for(i=0; i<=ssize-1; i++)
    {
        sparsegetrow(xy, i, &network->xyrow, _state);
        for(j=0; j<=jmax; j++)
        {
            sigmas.ptr.p_double[j] = sigmas.ptr.p_double[j]+ae_sqr(network->xyrow.ptr.p_double[j]-means.ptr.p_double[j], _state);
        }
    }
    for(i=0; i<=jmax; i++)
    {
        sigmas.ptr.p_double[i] = ae_sqrt(sigmas.ptr.p_double[i]/ssize, _state);
    }
    
    /*
     * Inputs
     */
    for(i=0; i<=nin-1; i++)
    {
        network->columnmeans.ptr.p_double[i] = means.ptr.p_double[i];
        network->columnsigmas.ptr.p_double[i] = sigmas.ptr.p_double[i];
        if( ae_fp_eq(network->columnsigmas.ptr.p_double[i],0) )
        {
            network->columnsigmas.ptr.p_double[i] = 1;
        }
    }
    
    /*
     * Outputs
     */
    if( !mlpissoftmax(network, _state) )
    {
        for(i=0; i<=nout-1; i++)
        {
            offs = istart+(ntotal-nout+i)*mlpbase_nfieldwidth;
            ntype = network->structinfo.ptr.p_int[offs+0];
            
            /*
             * Linear outputs
             */
            if( ntype==0 )
            {
                network->columnmeans.ptr.p_double[nin+i] = means.ptr.p_double[nin+i];
                network->columnsigmas.ptr.p_double[nin+i] = sigmas.ptr.p_double[nin+i];
                if( ae_fp_eq(network->columnsigmas.ptr.p_double[nin+i],0) )
                {
                    network->columnsigmas.ptr.p_double[nin+i] = 1;
                }
            }
            
            /*
             * Bounded outputs (half-interval)
             */
            if( ntype==3 )
            {
                s = means.ptr.p_double[nin+i]-network->columnmeans.ptr.p_double[nin+i];
                if( ae_fp_eq(s,0) )
                {
                    s = ae_sign(network->columnsigmas.ptr.p_double[nin+i], _state);
                }
                if( ae_fp_eq(s,0) )
                {
                    s = 1.0;
                }
                network->columnsigmas.ptr.p_double[nin+i] = ae_sign(network->columnsigmas.ptr.p_double[nin+i], _state)*ae_fabs(s, _state);
                if( ae_fp_eq(network->columnsigmas.ptr.p_double[nin+i],0) )
                {
                    network->columnsigmas.ptr.p_double[nin+i] = 1;
                }
            }
        }
    }
    ae_frame_leave(_state);
}


/*************************************************************************
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.

  -- ALGLIB --
     Copyright 23.08.2012 by Bochkanov Sergey
*************************************************************************/
void mlpinitpreprocessorsubset(multilayerperceptron* network,
     /* Real    */ ae_matrix* xy,
     ae_int_t setsize,
     /* Integer */ ae_vector* idx,
     ae_int_t subsetsize,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t jmax;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    ae_int_t ntotal;
    ae_int_t istart;
    ae_int_t offs;
    ae_int_t ntype;
    ae_vector means;
    ae_vector sigmas;
    double s;
    ae_int_t npoints;
    ae_int_t i;
    ae_int_t j;

    ae_frame_make(_state, &_frame_block);
    ae_vector_init(&means, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&sigmas, 0, DT_REAL, _state, ae_true);

    ae_assert(setsize>=0, "MLPInitPreprocessorSubset: SetSize<0", _state);
    if( subsetsize<0 )
    {
        mlpinitpreprocessor(network, xy, setsize, _state);
        ae_frame_leave(_state);
        return;
    }
    ae_assert(subsetsize<=idx->cnt, "MLPInitPreprocessorSubset: SubsetSize>Length(Idx)", _state);
    npoints = setsize;
    for(i=0; i<=subsetsize-1; i++)
    {
        ae_assert(idx->ptr.p_int[i]>=0, "MLPInitPreprocessorSubset: incorrect index of XY row(Idx[I]<0)", _state);
        ae_assert(idx->ptr.p_int[i]<=npoints-1, "MLPInitPreprocessorSubset: incorrect index of XY row(Idx[I]>Rows(XY)-1)", _state);
    }
    mlpproperties(network, &nin, &nout, &wcount, _state);
    ntotal = network->structinfo.ptr.p_int[3];
    istart = network->structinfo.ptr.p_int[5];
    
    /*
     * Means/Sigmas
     */
    if( mlpissoftmax(network, _state) )
    {
        jmax = nin-1;
    }
    else
    {
        jmax = nin+nout-1;
    }
    ae_vector_set_length(&means, jmax+1, _state);
    ae_vector_set_length(&sigmas, jmax+1, _state);
    for(i=0; i<=jmax; i++)
    {
        means.ptr.p_double[i] = 0;
        sigmas.ptr.p_double[i] = 0;
    }
    for(i=0; i<=subsetsize-1; i++)
    {
        for(j=0; j<=jmax; j++)
        {
            means.ptr.p_double[j] = means.ptr.p_double[j]+xy->ptr.pp_double[idx->ptr.p_int[i]][j];
        }
    }
    for(i=0; i<=jmax; i++)
    {
        means.ptr.p_double[i] = means.ptr.p_double[i]/subsetsize;
    }
    for(i=0; i<=subsetsize-1; i++)
    {
        for(j=0; j<=jmax; j++)
        {
            sigmas.ptr.p_double[j] = sigmas.ptr.p_double[j]+ae_sqr(xy->ptr.pp_double[idx->ptr.p_int[i]][j]-means.ptr.p_double[j], _state);
        }
    }
    for(i=0; i<=jmax; i++)
    {
        sigmas.ptr.p_double[i] = ae_sqrt(sigmas.ptr.p_double[i]/subsetsize, _state);
    }
    
    /*
     * Inputs
     */
    for(i=0; i<=nin-1; i++)
    {
        network->columnmeans.ptr.p_double[i] = means.ptr.p_double[i];
        network->columnsigmas.ptr.p_double[i] = sigmas.ptr.p_double[i];
        if( ae_fp_eq(network->columnsigmas.ptr.p_double[i],0) )
        {
            network->columnsigmas.ptr.p_double[i] = 1;
        }
    }
    
    /*
     * Outputs
     */
    if( !mlpissoftmax(network, _state) )
    {
        for(i=0; i<=nout-1; i++)
        {
            offs = istart+(ntotal-nout+i)*mlpbase_nfieldwidth;
            ntype = network->structinfo.ptr.p_int[offs+0];
            
            /*
             * Linear outputs
             */
            if( ntype==0 )
            {
                network->columnmeans.ptr.p_double[nin+i] = means.ptr.p_double[nin+i];
                network->columnsigmas.ptr.p_double[nin+i] = sigmas.ptr.p_double[nin+i];
                if( ae_fp_eq(network->columnsigmas.ptr.p_double[nin+i],0) )
                {
                    network->columnsigmas.ptr.p_double[nin+i] = 1;
                }
            }
            
            /*
             * Bounded outputs (half-interval)
             */
            if( ntype==3 )
            {
                s = means.ptr.p_double[nin+i]-network->columnmeans.ptr.p_double[nin+i];
                if( ae_fp_eq(s,0) )
                {
                    s = ae_sign(network->columnsigmas.ptr.p_double[nin+i], _state);
                }
                if( ae_fp_eq(s,0) )
                {
                    s = 1.0;
                }
                network->columnsigmas.ptr.p_double[nin+i] = ae_sign(network->columnsigmas.ptr.p_double[nin+i], _state)*ae_fabs(s, _state);
                if( ae_fp_eq(network->columnsigmas.ptr.p_double[nin+i],0) )
                {
                    network->columnsigmas.ptr.p_double[nin+i] = 1;
                }
            }
        }
    }
    ae_frame_leave(_state);
}


/*************************************************************************
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.
      
  -- ALGLIB --
     Copyright 26.07.2012 by Bochkanov Sergey
*************************************************************************/
void mlpinitpreprocessorsparsesubset(multilayerperceptron* network,
     sparsematrix* xy,
     ae_int_t setsize,
     /* Integer */ ae_vector* idx,
     ae_int_t subsetsize,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t jmax;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    ae_int_t ntotal;
    ae_int_t istart;
    ae_int_t offs;
    ae_int_t ntype;
    ae_vector means;
    ae_vector sigmas;
    double s;
    ae_int_t npoints;
    ae_int_t i;
    ae_int_t j;

    ae_frame_make(_state, &_frame_block);
    ae_vector_init(&means, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&sigmas, 0, DT_REAL, _state, ae_true);

    ae_assert(setsize>=0, "MLPInitPreprocessorSparseSubset: SetSize<0", _state);
    if( subsetsize<0 )
    {
        mlpinitpreprocessorsparse(network, xy, setsize, _state);
        ae_frame_leave(_state);
        return;
    }
    ae_assert(subsetsize<=idx->cnt, "MLPInitPreprocessorSparseSubset: SubsetSize>Length(Idx)", _state);
    npoints = setsize;
    for(i=0; i<=subsetsize-1; i++)
    {
        ae_assert(idx->ptr.p_int[i]>=0, "MLPInitPreprocessorSparseSubset: incorrect index of XY row(Idx[I]<0)", _state);
        ae_assert(idx->ptr.p_int[i]<=npoints-1, "MLPInitPreprocessorSparseSubset: incorrect index of XY row(Idx[I]>Rows(XY)-1)", _state);
    }
    mlpproperties(network, &nin, &nout, &wcount, _state);
    ntotal = network->structinfo.ptr.p_int[3];
    istart = network->structinfo.ptr.p_int[5];
    
    /*
     * Means/Sigmas
     */
    if( mlpissoftmax(network, _state) )
    {
        jmax = nin-1;
    }
    else
    {
        jmax = nin+nout-1;
    }
    ae_vector_set_length(&means, jmax+1, _state);
    ae_vector_set_length(&sigmas, jmax+1, _state);
    for(i=0; i<=jmax; i++)
    {
        means.ptr.p_double[i] = 0;
        sigmas.ptr.p_double[i] = 0;
    }
    for(i=0; i<=subsetsize-1; i++)
    {
        sparsegetrow(xy, idx->ptr.p_int[i], &network->xyrow, _state);
        for(j=0; j<=jmax; j++)
        {
            means.ptr.p_double[j] = means.ptr.p_double[j]+network->xyrow.ptr.p_double[j];
        }
    }
    for(i=0; i<=jmax; i++)
    {
        means.ptr.p_double[i] = means.ptr.p_double[i]/subsetsize;
    }
    for(i=0; i<=subsetsize-1; i++)
    {
        sparsegetrow(xy, idx->ptr.p_int[i], &network->xyrow, _state);
        for(j=0; j<=jmax; j++)
        {
            sigmas.ptr.p_double[j] = sigmas.ptr.p_double[j]+ae_sqr(network->xyrow.ptr.p_double[j]-means.ptr.p_double[j], _state);
        }
    }
    for(i=0; i<=jmax; i++)
    {
        sigmas.ptr.p_double[i] = ae_sqrt(sigmas.ptr.p_double[i]/subsetsize, _state);
    }
    
    /*
     * Inputs
     */
    for(i=0; i<=nin-1; i++)
    {
        network->columnmeans.ptr.p_double[i] = means.ptr.p_double[i];
        network->columnsigmas.ptr.p_double[i] = sigmas.ptr.p_double[i];
        if( ae_fp_eq(network->columnsigmas.ptr.p_double[i],0) )
        {
            network->columnsigmas.ptr.p_double[i] = 1;
        }
    }
    
    /*
     * Outputs
     */
    if( !mlpissoftmax(network, _state) )
    {
        for(i=0; i<=nout-1; i++)
        {
            offs = istart+(ntotal-nout+i)*mlpbase_nfieldwidth;
            ntype = network->structinfo.ptr.p_int[offs+0];
            
            /*
             * Linear outputs
             */
            if( ntype==0 )
            {
                network->columnmeans.ptr.p_double[nin+i] = means.ptr.p_double[nin+i];
                network->columnsigmas.ptr.p_double[nin+i] = sigmas.ptr.p_double[nin+i];
                if( ae_fp_eq(network->columnsigmas.ptr.p_double[nin+i],0) )
                {
                    network->columnsigmas.ptr.p_double[nin+i] = 1;
                }
            }
            
            /*
             * Bounded outputs (half-interval)
             */
            if( ntype==3 )
            {
                s = means.ptr.p_double[nin+i]-network->columnmeans.ptr.p_double[nin+i];
                if( ae_fp_eq(s,0) )
                {
                    s = ae_sign(network->columnsigmas.ptr.p_double[nin+i], _state);
                }
                if( ae_fp_eq(s,0) )
                {
                    s = 1.0;
                }
                network->columnsigmas.ptr.p_double[nin+i] = ae_sign(network->columnsigmas.ptr.p_double[nin+i], _state)*ae_fabs(s, _state);
                if( ae_fp_eq(network->columnsigmas.ptr.p_double[nin+i],0) )
                {
                    network->columnsigmas.ptr.p_double[nin+i] = 1;
                }
            }
        }
    }
    ae_frame_leave(_state);
}


/*************************************************************************
Returns information about initialized network: number of inputs, outputs,
weights.

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
void mlpproperties(multilayerperceptron* network,
     ae_int_t* nin,
     ae_int_t* nout,
     ae_int_t* wcount,
     ae_state *_state)
{

    *nin = 0;
    *nout = 0;
    *wcount = 0;

    *nin = network->structinfo.ptr.p_int[1];
    *nout = network->structinfo.ptr.p_int[2];
    *wcount = network->structinfo.ptr.p_int[4];
}


/*************************************************************************
Returns number of inputs.

  -- ALGLIB --
     Copyright 19.10.2011 by Bochkanov Sergey
*************************************************************************/
ae_int_t mlpgetinputscount(multilayerperceptron* network,
     ae_state *_state)
{
    ae_int_t result;


    result = network->structinfo.ptr.p_int[1];
    return result;
}


/*************************************************************************
Returns number of outputs.

  -- ALGLIB --
     Copyright 19.10.2011 by Bochkanov Sergey
*************************************************************************/
ae_int_t mlpgetoutputscount(multilayerperceptron* network,
     ae_state *_state)
{
    ae_int_t result;


    result = network->structinfo.ptr.p_int[2];
    return result;
}


/*************************************************************************
Returns number of weights.

  -- ALGLIB --
     Copyright 19.10.2011 by Bochkanov Sergey
*************************************************************************/
ae_int_t mlpgetweightscount(multilayerperceptron* network,
     ae_state *_state)
{
    ae_int_t result;


    result = network->structinfo.ptr.p_int[4];
    return result;
}


/*************************************************************************
Tells whether network is SOFTMAX-normalized (i.e. classifier) or not.

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
ae_bool mlpissoftmax(multilayerperceptron* network, ae_state *_state)
{
    ae_bool result;


    result = network->structinfo.ptr.p_int[6]==1;
    return result;
}


/*************************************************************************
This function returns total number of layers (including input, hidden and
output layers).

  -- ALGLIB --
     Copyright 25.03.2011 by Bochkanov Sergey
*************************************************************************/
ae_int_t mlpgetlayerscount(multilayerperceptron* network,
     ae_state *_state)
{
    ae_int_t result;


    result = network->hllayersizes.cnt;
    return result;
}


/*************************************************************************
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.

  -- ALGLIB --
     Copyright 25.03.2011 by Bochkanov Sergey
*************************************************************************/
ae_int_t mlpgetlayersize(multilayerperceptron* network,
     ae_int_t k,
     ae_state *_state)
{
    ae_int_t result;


    ae_assert(k>=0&&k<network->hllayersizes.cnt, "MLPGetLayerSize: incorrect layer index", _state);
    result = network->hllayersizes.ptr.p_int[k];
    return result;
}


/*************************************************************************
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

  -- ALGLIB --
     Copyright 25.03.2011 by Bochkanov Sergey
*************************************************************************/
void mlpgetinputscaling(multilayerperceptron* network,
     ae_int_t i,
     double* mean,
     double* sigma,
     ae_state *_state)
{

    *mean = 0;
    *sigma = 0;

    ae_assert(i>=0&&i<network->hllayersizes.ptr.p_int[0], "MLPGetInputScaling: incorrect (nonexistent) I", _state);
    *mean = network->columnmeans.ptr.p_double[i];
    *sigma = network->columnsigmas.ptr.p_double[i];
    if( ae_fp_eq(*sigma,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).

  -- ALGLIB --
     Copyright 25.03.2011 by Bochkanov Sergey
*************************************************************************/
void mlpgetoutputscaling(multilayerperceptron* network,
     ae_int_t i,
     double* mean,
     double* sigma,
     ae_state *_state)
{

    *mean = 0;
    *sigma = 0;

    ae_assert(i>=0&&i<network->hllayersizes.ptr.p_int[network->hllayersizes.cnt-1], "MLPGetOutputScaling: incorrect (nonexistent) I", _state);
    if( network->structinfo.ptr.p_int[6]==1 )
    {
        *mean = 0;
        *sigma = 1;
    }
    else
    {
        *mean = network->columnmeans.ptr.p_double[network->hllayersizes.ptr.p_int[0]+i];
        *sigma = network->columnsigmas.ptr.p_double[network->hllayersizes.ptr.p_int[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.

  -- ALGLIB --
     Copyright 25.03.2011 by Bochkanov Sergey
*************************************************************************/
void mlpgetneuroninfo(multilayerperceptron* network,
     ae_int_t k,
     ae_int_t i,
     ae_int_t* fkind,
     double* threshold,
     ae_state *_state)
{
    ae_int_t ncnt;
    ae_int_t istart;
    ae_int_t highlevelidx;
    ae_int_t activationoffset;

    *fkind = 0;
    *threshold = 0;

    ncnt = network->hlneurons.cnt/mlpbase_hlnfieldwidth;
    istart = network->structinfo.ptr.p_int[5];
    
    /*
     * search
     */
    network->integerbuf.ptr.p_int[0] = k;
    network->integerbuf.ptr.p_int[1] = i;
    highlevelidx = recsearch(&network->hlneurons, mlpbase_hlnfieldwidth, 2, 0, ncnt, &network->integerbuf, _state);
    ae_assert(highlevelidx>=0, "MLPGetNeuronInfo: incorrect (nonexistent) layer or neuron index", _state);
    
    /*
     * 1. find offset of the activation function record in the
     */
    if( network->hlneurons.ptr.p_int[highlevelidx*mlpbase_hlnfieldwidth+2]>=0 )
    {
        activationoffset = istart+network->hlneurons.ptr.p_int[highlevelidx*mlpbase_hlnfieldwidth+2]*mlpbase_nfieldwidth;
        *fkind = network->structinfo.ptr.p_int[activationoffset+0];
    }
    else
    {
        *fkind = 0;
    }
    if( network->hlneurons.ptr.p_int[highlevelidx*mlpbase_hlnfieldwidth+3]>=0 )
    {
        *threshold = network->weights.ptr.p_double[network->hlneurons.ptr.p_int[highlevelidx*mlpbase_hlnfieldwidth+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

  -- ALGLIB --
     Copyright 25.03.2011 by Bochkanov Sergey
*************************************************************************/
double mlpgetweight(multilayerperceptron* network,
     ae_int_t k0,
     ae_int_t i0,
     ae_int_t k1,
     ae_int_t i1,
     ae_state *_state)
{
    ae_int_t ccnt;
    ae_int_t highlevelidx;
    double result;


    ccnt = network->hlconnections.cnt/mlpbase_hlconnfieldwidth;
    
    /*
     * check params
     */
    ae_assert(k0>=0&&k0<network->hllayersizes.cnt, "MLPGetWeight: incorrect (nonexistent) K0", _state);
    ae_assert(i0>=0&&i0<network->hllayersizes.ptr.p_int[k0], "MLPGetWeight: incorrect (nonexistent) I0", _state);
    ae_assert(k1>=0&&k1<network->hllayersizes.cnt, "MLPGetWeight: incorrect (nonexistent) K1", _state);
    ae_assert(i1>=0&&i1<network->hllayersizes.ptr.p_int[k1], "MLPGetWeight: incorrect (nonexistent) I1", _state);
    
    /*
     * search
     */
    network->integerbuf.ptr.p_int[0] = k0;
    network->integerbuf.ptr.p_int[1] = i0;
    network->integerbuf.ptr.p_int[2] = k1;
    network->integerbuf.ptr.p_int[3] = i1;
    highlevelidx = recsearch(&network->hlconnections, mlpbase_hlconnfieldwidth, 4, 0, ccnt, &network->integerbuf, _state);
    if( highlevelidx>=0 )
    {
        result = network->weights.ptr.p_double[network->hlconnections.ptr.p_int[highlevelidx*mlpbase_hlconnfieldwidth+4]];
    }
    else
    {
        result = 0;
    }
    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.

  -- ALGLIB --
     Copyright 25.03.2011 by Bochkanov Sergey
*************************************************************************/
void mlpsetinputscaling(multilayerperceptron* network,
     ae_int_t i,
     double mean,
     double sigma,
     ae_state *_state)
{


    ae_assert(i>=0&&i<network->hllayersizes.ptr.p_int[0], "MLPSetInputScaling: incorrect (nonexistent) I", _state);
    ae_assert(ae_isfinite(mean, _state), "MLPSetInputScaling: infinite or NAN Mean", _state);
    ae_assert(ae_isfinite(sigma, _state), "MLPSetInputScaling: infinite or NAN Sigma", _state);
    if( ae_fp_eq(sigma,0) )
    {
        sigma = 1;
    }
    network->columnmeans.ptr.p_double[i] = mean;
    network->columnsigmas.ptr.p_double[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.

  -- ALGLIB --
     Copyright 25.03.2011 by Bochkanov Sergey
*************************************************************************/
void mlpsetoutputscaling(multilayerperceptron* network,
     ae_int_t i,
     double mean,
     double sigma,
     ae_state *_state)
{


    ae_assert(i>=0&&i<network->hllayersizes.ptr.p_int[network->hllayersizes.cnt-1], "MLPSetOutputScaling: incorrect (nonexistent) I", _state);
    ae_assert(ae_isfinite(mean, _state), "MLPSetOutputScaling: infinite or NAN Mean", _state);
    ae_assert(ae_isfinite(sigma, _state), "MLPSetOutputScaling: infinite or NAN Sigma", _state);
    if( network->structinfo.ptr.p_int[6]==1 )
    {
        ae_assert(ae_fp_eq(mean,0), "MLPSetOutputScaling: you can not set non-zero Mean term for classifier network", _state);
        ae_assert(ae_fp_eq(sigma,1), "MLPSetOutputScaling: you can not set non-unit Sigma term for classifier network", _state);
    }
    else
    {
        if( ae_fp_eq(sigma,0) )
        {
            sigma = 1;
        }
        network->columnmeans.ptr.p_double[network->hllayersizes.ptr.p_int[0]+i] = mean;
        network->columnsigmas.ptr.p_double[network->hllayersizes.ptr.p_int[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).

  -- ALGLIB --
     Copyright 25.03.2011 by Bochkanov Sergey
*************************************************************************/
void mlpsetneuroninfo(multilayerperceptron* network,
     ae_int_t k,
     ae_int_t i,
     ae_int_t fkind,
     double threshold,
     ae_state *_state)
{
    ae_int_t ncnt;
    ae_int_t istart;
    ae_int_t highlevelidx;
    ae_int_t activationoffset;


    ae_assert(ae_isfinite(threshold, _state), "MLPSetNeuronInfo: infinite or NAN Threshold", _state);
    
    /*
     * convenience vars
     */
    ncnt = network->hlneurons.cnt/mlpbase_hlnfieldwidth;
    istart = network->structinfo.ptr.p_int[5];
    
    /*
     * search
     */
    network->integerbuf.ptr.p_int[0] = k;
    network->integerbuf.ptr.p_int[1] = i;
    highlevelidx = recsearch(&network->hlneurons, mlpbase_hlnfieldwidth, 2, 0, ncnt, &network->integerbuf, _state);
    ae_assert(highlevelidx>=0, "MLPSetNeuronInfo: incorrect (nonexistent) layer or neuron index", _state);
    
    /*
     * activation function
     */
    if( network->hlneurons.ptr.p_int[highlevelidx*mlpbase_hlnfieldwidth+2]>=0 )
    {
        activationoffset = istart+network->hlneurons.ptr.p_int[highlevelidx*mlpbase_hlnfieldwidth+2]*mlpbase_nfieldwidth;
        network->structinfo.ptr.p_int[activationoffset+0] = fkind;
    }
    else
    {
        ae_assert(fkind==0, "MLPSetNeuronInfo: you try to set activation function for neuron which can not have one", _state);
    }
    
    /*
     * Threshold
     */
    if( network->hlneurons.ptr.p_int[highlevelidx*mlpbase_hlnfieldwidth+3]>=0 )
    {
        network->weights.ptr.p_double[network->hlneurons.ptr.p_int[highlevelidx*mlpbase_hlnfieldwidth+3]] = threshold;
    }
    else
    {
        ae_assert(ae_fp_eq(threshold,0), "MLPSetNeuronInfo: you try to set non-zero threshold for neuron which can not have one", _state);
    }
}


/*************************************************************************
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

  -- ALGLIB --
     Copyright 25.03.2011 by Bochkanov Sergey
*************************************************************************/
void mlpsetweight(multilayerperceptron* network,
     ae_int_t k0,
     ae_int_t i0,
     ae_int_t k1,
     ae_int_t i1,
     double w,
     ae_state *_state)
{
    ae_int_t ccnt;
    ae_int_t highlevelidx;


    ccnt = network->hlconnections.cnt/mlpbase_hlconnfieldwidth;
    
    /*
     * check params
     */
    ae_assert(k0>=0&&k0<network->hllayersizes.cnt, "MLPSetWeight: incorrect (nonexistent) K0", _state);
    ae_assert(i0>=0&&i0<network->hllayersizes.ptr.p_int[k0], "MLPSetWeight: incorrect (nonexistent) I0", _state);
    ae_assert(k1>=0&&k1<network->hllayersizes.cnt, "MLPSetWeight: incorrect (nonexistent) K1", _state);
    ae_assert(i1>=0&&i1<network->hllayersizes.ptr.p_int[k1], "MLPSetWeight: incorrect (nonexistent) I1", _state);
    ae_assert(ae_isfinite(w, _state), "MLPSetWeight: infinite or NAN weight", _state);
    
    /*
     * search
     */
    network->integerbuf.ptr.p_int[0] = k0;
    network->integerbuf.ptr.p_int[1] = i0;
    network->integerbuf.ptr.p_int[2] = k1;
    network->integerbuf.ptr.p_int[3] = i1;
    highlevelidx = recsearch(&network->hlconnections, mlpbase_hlconnfieldwidth, 4, 0, ccnt, &network->integerbuf, _state);
    if( highlevelidx>=0 )
    {
        network->weights.ptr.p_double[network->hlconnections.ptr.p_int[highlevelidx*mlpbase_hlconnfieldwidth+4]] = w;
    }
    else
    {
        ae_assert(ae_fp_eq(w,0), "MLPSetWeight: you try to set non-zero weight for non-existent connection", _state);
    }
}


/*************************************************************************
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

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
void mlpactivationfunction(double net,
     ae_int_t k,
     double* f,
     double* df,
     double* d2f,
     ae_state *_state)
{
    double net2;
    double arg;
    double root;
    double r;

    *f = 0;
    *df = 0;
    *d2f = 0;

    if( k==0||k==-5 )
    {
        *f = net;
        *df = 1;
        *d2f = 0;
        return;
    }
    if( k==1 )
    {
        
        /*
         * TanH activation function
         */
        if( ae_fp_less(ae_fabs(net, _state),100) )
        {
            *f = ae_tanh(net, _state);
        }
        else
        {
            *f = ae_sign(net, _state);
        }
        *df = 1-ae_sqr(*f, _state);
        *d2f = -2*(*f)*(*df);
        return;
    }
    if( k==3 )
    {
        
        /*
         * EX activation function
         */
        if( ae_fp_greater_eq(net,0) )
        {
            net2 = net*net;
            arg = net2+1;
            root = ae_sqrt(arg, _state);
            *f = net+root;
            r = net/root;
            *df = 1+r;
            *d2f = (root-net*r)/arg;
        }
        else
        {
            *f = ae_exp(net, _state);
            *df = *f;
            *d2f = *f;
        }
        return;
    }
    if( k==2 )
    {
        *f = ae_exp(-ae_sqr(net, _state), _state);
        *df = -2*net*(*f);
        *d2f = -2*(*f+*df*net);
        return;
    }
    *f = 0;
    *df = 0;
    *d2f = 0;
}


/*************************************************************************
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

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
void mlpprocess(multilayerperceptron* network,
     /* Real    */ ae_vector* x,
     /* Real    */ ae_vector* y,
     ae_state *_state)
{


    if( y->cnt<network->structinfo.ptr.p_int[2] )
    {
        ae_vector_set_length(y, network->structinfo.ptr.p_int[2], _state);
    }
    mlpinternalprocessvector(&network->structinfo, &network->weights, &network->columnmeans, &network->columnsigmas, &network->neurons, &network->dfdnet, x, y, _state);
}


/*************************************************************************
'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.

  -- ALGLIB --
     Copyright 21.09.2010 by Bochkanov Sergey
*************************************************************************/
void mlpprocessi(multilayerperceptron* network,
     /* Real    */ ae_vector* x,
     /* Real    */ ae_vector* y,
     ae_state *_state)
{

    ae_vector_clear(y);

    mlpprocess(network, x, y, _state);
}


/*************************************************************************
Error of the neural network on dataset.

INPUT PARAMETERS:
    Network     -   neural network;
    XY          -   training  set,  see  below  for  information  on   the
                    training set format;
    SSize       -   points count.

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).

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
double mlperror(multilayerperceptron* network,
     /* Real    */ ae_matrix* xy,
     ae_int_t ssize,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t k;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    double e;
    double result;


    mlpproperties(network, &nin, &nout, &wcount, _state);
    result = 0;
    for(i=0; i<=ssize-1; i++)
    {
        ae_v_move(&network->x.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nin-1));
        mlpprocess(network, &network->x, &network->y, _state);
        if( mlpissoftmax(network, _state) )
        {
            
            /*
             * class labels outputs
             */
            k = ae_round(xy->ptr.pp_double[i][nin], _state);
            if( k>=0&&k<nout )
            {
                network->y.ptr.p_double[k] = network->y.ptr.p_double[k]-1;
            }
        }
        else
        {
            
            /*
             * real outputs
             */
            ae_v_sub(&network->y.ptr.p_double[0], 1, &xy->ptr.pp_double[i][nin], 1, ae_v_len(0,nout-1));
        }
        e = ae_v_dotproduct(&network->y.ptr.p_double[0], 1, &network->y.ptr.p_double[0], 1, ae_v_len(0,nout-1));
        result = result+e/2;
    }
    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).
  
  -- ALGLIB --
     Copyright 23.07.2012 by Bochkanov Sergey
*************************************************************************/
double mlperrorsparse(multilayerperceptron* network,
     sparsematrix* xy,
     ae_int_t npoints,
     ae_state *_state)
{
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    double e;
    ae_int_t t0;
    ae_int_t t1;
    ae_bool iscls;
    ae_int_t i;
    ae_int_t j;
    double result;


    ae_assert(npoints>=0, "MLPErrorSparse: NPoints<0.", _state);
    if( npoints==0 )
    {
        result = 0;
        return result;
    }
    ae_assert(sparseiscrs(xy, _state), "MLPErrorSparse: sparse matrix XY has not CRS format.", _state);
    
    /*
     * Check dataset correctness
     */
    t0 = 0;
    t1 = 0;
    mlpproperties(network, &nin, &nout, &wcount, _state);
    iscls = mlpissoftmax(network, _state);
    if( !iscls )
    {
        while(sparseenumerate(xy, &t0, &t1, &i, &j, &e, _state))
        {
            ae_assert(ae_isfinite(e, _state), "MLPErrorSparse: sparse matrix XY contains Infinite or NaN.", _state);
        }
    }
    else
    {
        while(sparseenumerate(xy, &t0, &t1, &i, &j, &e, _state))
        {
            if( j!=nin )
            {
                ae_assert(ae_isfinite(e, _state), "MLPErrorSparse: sparse matrix XY contains Infinite or NaN.", _state);
            }
            else
            {
                ae_assert((ae_isfinite(e, _state)&&ae_round(e, _state)>=0)&&ae_round(e, _state)<nout, "MLPErrorSparse: invalid sparse matrix XY(in classifier used nonexistent class number: either XY[.,NIn]<0 or XY[.,NIn]>=NClasses).", _state);
            }
        }
    }
    result = 0;
    for(i=0; i<=npoints-1; i++)
    {
        sparsegetrow(xy, i, &network->xyrow, _state);
        ae_v_move(&network->x.ptr.p_double[0], 1, &network->xyrow.ptr.p_double[0], 1, ae_v_len(0,nin-1));
        mlpprocess(network, &network->x, &network->y, _state);
        if( iscls )
        {
            
            /*
             * Class labels outputs
             */
            j = ae_round(network->xyrow.ptr.p_double[nin], _state);
            ae_assert(j>=0&&j<nout, "MLPErrorSparse: internal error(either J<0 or J>=NOut when J is class number)", _state);
            network->y.ptr.p_double[j] = network->y.ptr.p_double[j]-1;
        }
        else
        {
            
            /*
             * Real outputs
             */
            ae_v_sub(&network->y.ptr.p_double[0], 1, &network->xyrow.ptr.p_double[nin], 1, ae_v_len(0,nout-1));
        }
        e = ae_v_dotproduct(&network->y.ptr.p_double[0], 1, &network->y.ptr.p_double[0], 1, ae_v_len(0,nout-1));
        result = result+e/2;
    }
    return result;
}


/*************************************************************************
Natural error function for neural network, internal subroutine.

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
double mlperrorn(multilayerperceptron* network,
     /* Real    */ ae_matrix* xy,
     ae_int_t ssize,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t k;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    double e;
    double result;


    mlpproperties(network, &nin, &nout, &wcount, _state);
    result = 0;
    for(i=0; i<=ssize-1; i++)
    {
        
        /*
         * Process vector
         */
        ae_v_move(&network->x.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nin-1));
        mlpprocess(network, &network->x, &network->y, _state);
        
        /*
         * Update error function
         */
        if( network->structinfo.ptr.p_int[6]==0 )
        {
            
            /*
             * Least squares error function
             */
            ae_v_sub(&network->y.ptr.p_double[0], 1, &xy->ptr.pp_double[i][nin], 1, ae_v_len(0,nout-1));
            e = ae_v_dotproduct(&network->y.ptr.p_double[0], 1, &network->y.ptr.p_double[0], 1, ae_v_len(0,nout-1));
            result = result+e/2;
        }
        else
        {
            
            /*
             * Cross-entropy error function
             */
            k = ae_round(xy->ptr.pp_double[i][nin], _state);
            if( k>=0&&k<nout )
            {
                result = result+mlpbase_safecrossentropy(1, network->y.ptr.p_double[k], _state);
            }
        }
    }
    return result;
}


/*************************************************************************
Classification error

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
ae_int_t mlpclserror(multilayerperceptron* network,
     /* Real    */ ae_matrix* xy,
     ae_int_t ssize,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t i;
    ae_int_t j;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    ae_vector workx;
    ae_vector worky;
    ae_int_t nn;
    ae_int_t ns;
    ae_int_t nmax;
    ae_int_t result;

    ae_frame_make(_state, &_frame_block);
    ae_vector_init(&workx, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&worky, 0, DT_REAL, _state, ae_true);

    mlpproperties(network, &nin, &nout, &wcount, _state);
    ae_vector_set_length(&workx, nin-1+1, _state);
    ae_vector_set_length(&worky, nout-1+1, _state);
    result = 0;
    for(i=0; i<=ssize-1; i++)
    {
        
        /*
         * Process
         */
        ae_v_move(&workx.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nin-1));
        mlpprocess(network, &workx, &worky, _state);
        
        /*
         * Network version of the answer
         */
        nmax = 0;
        for(j=0; j<=nout-1; j++)
        {
            if( ae_fp_greater(worky.ptr.p_double[j],worky.ptr.p_double[nmax]) )
            {
                nmax = j;
            }
        }
        nn = nmax;
        
        /*
         * Right answer
         */
        if( mlpissoftmax(network, _state) )
        {
            ns = ae_round(xy->ptr.pp_double[i][nin], _state);
        }
        else
        {
            nmax = 0;
            for(j=0; j<=nout-1; j++)
            {
                if( ae_fp_greater(xy->ptr.pp_double[i][nin+j],xy->ptr.pp_double[i][nin+nmax]) )
                {
                    nmax = j;
                }
            }
            ns = nmax;
        }
        
        /*
         * compare
         */
        if( nn!=ns )
        {
            result = result+1;
        }
    }
    ae_frame_leave(_state);
    return result;
}


/*************************************************************************
Relative classification error on the test set.

INPUT PARAMETERS:
    Network     -   neural network;
    XY          -   training  set,  see  below  for  information  on   the
                    training set format;
    NPoints     -   points count.

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).

  -- ALGLIB --
     Copyright 25.12.2008 by Bochkanov Sergey
*************************************************************************/
double mlprelclserror(multilayerperceptron* network,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_state *_state)
{
    double result;


    result = (double)mlpclserror(network, xy, npoints, _state)/(double)npoints;
    return result;
}


/*************************************************************************
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. 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:
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).
  
  -- ALGLIB --
     Copyright 09.08.2012 by Bochkanov Sergey
*************************************************************************/
double mlprelclserrorsparse(multilayerperceptron* network,
     sparsematrix* xy,
     ae_int_t npoints,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    double e;
    ae_int_t t0;
    ae_int_t t1;
    ae_bool iscls;
    ae_vector workx;
    ae_vector worky;
    ae_int_t nn;
    ae_int_t ns;
    ae_int_t nmax;
    ae_int_t i;
    ae_int_t j;
    double result;

    ae_frame_make(_state, &_frame_block);
    ae_vector_init(&workx, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&worky, 0, DT_REAL, _state, ae_true);

    ae_assert(npoints>=0, "MLPRelClsErrorSparse: NPoints<0.", _state);
    if( npoints==0 )
    {
        result = 0;
        ae_frame_leave(_state);
        return result;
    }
    ae_assert(sparseiscrs(xy, _state), "MLPRelClsErrorSparse: sparse matrix XY has not CRS format.", _state);
    
    /*
     * Check dataset correctness
     */
    t0 = 0;
    t1 = 0;
    mlpproperties(network, &nin, &nout, &wcount, _state);
    iscls = mlpissoftmax(network, _state);
    if( !iscls )
    {
        while(sparseenumerate(xy, &t0, &t1, &i, &j, &e, _state))
        {
            ae_assert(ae_isfinite(e, _state), "MLPRelClsErrorSparse: sparse matrix XY contains Infinite or NaN.", _state);
        }
    }
    else
    {
        while(sparseenumerate(xy, &t0, &t1, &i, &j, &e, _state))
        {
            if( j!=nin )
            {
                ae_assert(ae_isfinite(e, _state), "MLPRelClsErrorSparse: sparse matrix XY contains Infinite or NaN.", _state);
            }
            else
            {
                ae_assert((ae_isfinite(e, _state)&&ae_round(e, _state)>=0)&&ae_round(e, _state)<nout, "MLPRelClsErrorSparse: invalid sparse matrix XY(in classifier used nonexistent class number: either XY[.,NIn]<0 or XY[.,NIn]>=NClasses).", _state);
            }
        }
    }
    ae_vector_set_length(&workx, nin, _state);
    ae_vector_set_length(&worky, nout, _state);
    result = 0;
    for(i=0; i<=npoints-1; i++)
    {
        
        /*
         * Process
         */
        sparsegetrow(xy, i, &network->xyrow, _state);
        ae_v_move(&workx.ptr.p_double[0], 1, &network->xyrow.ptr.p_double[0], 1, ae_v_len(0,nin-1));
        mlpprocess(network, &workx, &worky, _state);
        
        /*
         * Network version of the answer
         */
        nmax = 0;
        for(j=0; j<=nout-1; j++)
        {
            if( ae_fp_greater(worky.ptr.p_double[j],worky.ptr.p_double[nmax]) )
            {
                nmax = j;
            }
        }
        nn = nmax;
        
        /*
         * Right answer
         */
        if( iscls )
        {
            ns = ae_round(network->xyrow.ptr.p_double[nin], _state);
        }
        else
        {
            nmax = 0;
            for(j=0; j<=nout-1; j++)
            {
                if( ae_fp_greater(network->xyrow.ptr.p_double[nin+j],network->xyrow.ptr.p_double[nin+nmax]) )
                {
                    nmax = j;
                }
            }
            ns = nmax;
        }
        
        /*
         * compare
         */
        if( nn!=ns )
        {
            result = result+1;
        }
    }
    result = result/npoints;
    ae_frame_leave(_state);
    return result;
}


/*************************************************************************
Average  cross-entropy  (in bits  per element)  on the  test set.

INPUT PARAMETERS:
    Network     -   neural network;
    XY          -   training  set,  see  below  for  information  on   the
                    training set format;
    NPoints     -   points count.

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).

  -- ALGLIB --
     Copyright 08.01.2009 by Bochkanov Sergey
*************************************************************************/
double mlpavgce(multilayerperceptron* network,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_state *_state)
{
    double result;


    if( mlpissoftmax(network, _state) )
    {
        result = mlperrorn(network, xy, npoints, _state)/(npoints*ae_log(2, _state));
    }
    else
    {
        result = 0;
    }
    return result;
}


/*************************************************************************
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).
  
  -- ALGLIB --
     Copyright 9.08.2012 by Bochkanov Sergey
*************************************************************************/
double mlpavgcesparse(multilayerperceptron* network,
     sparsematrix* xy,
     ae_int_t npoints,
     ae_state *_state)
{
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    double e;
    ae_int_t t0;
    ae_int_t t1;
    ae_int_t i;
    ae_int_t j;
    double result;


    ae_assert(npoints>=0, "MLPAvgCESparse: NPoints<0.", _state);
    if( npoints==0 )
    {
        result = 0;
        return result;
    }
    ae_assert(sparseiscrs(xy, _state), "MLPAvgCESparse: sparse matrix XY has not CRS format.", _state);
    
    /*
     * Check dataset correctness
     */
    t0 = 0;
    t1 = 0;
    mlpproperties(network, &nin, &nout, &wcount, _state);
    if( !mlpissoftmax(network, _state) )
    {
        while(sparseenumerate(xy, &t0, &t1, &i, &j, &e, _state))
        {
            ae_assert(ae_isfinite(e, _state), "MLPAvgCESparse: sparse matrix XY contains Infinite or NaN.", _state);
        }
    }
    else
    {
        while(sparseenumerate(xy, &t0, &t1, &i, &j, &e, _state))
        {
            if( j!=nin )
            {
                ae_assert(ae_isfinite(e, _state), "MLPAvgCESparse: sparse matrix XY contains Infinite or NaN.", _state);
            }
            else
            {
                ae_assert((ae_isfinite(e, _state)&&ae_round(e, _state)>=0)&&ae_round(e, _state)<nout, "MLPAvgCESparse: invalid sparse matrix XY(in classifier used nonexistent class number: either XY[.,NIn]<0 or XY[.,NIn]>=NClasses).", _state);
            }
        }
    }
    result = 0;
    if( mlpissoftmax(network, _state) )
    {
        for(i=0; i<=npoints-1; i++)
        {
            
            /*
             * Process vector
             */
            sparsegetrow(xy, i, &network->xyrow, _state);
            ae_v_move(&network->x.ptr.p_double[0], 1, &network->xyrow.ptr.p_double[0], 1, ae_v_len(0,nin-1));
            mlpprocess(network, &network->x, &network->y, _state);
            
            /*
             * Update cross-entropy error function
             */
            j = ae_round(network->xyrow.ptr.p_double[nin], _state);
            if( j>=0&&j<nout )
            {
                result = result+mlpbase_safecrossentropy(1, network->y.ptr.p_double[j], _state);
            }
        }
        result = result/(npoints*ae_log(2, _state));
    }
    return result;
}


/*************************************************************************
RMS error on the test set given.

INPUT PARAMETERS:
    Network     -   neural network;
    XY          -   training  set,  see  below  for  information  on   the
                    training set format;
    NPoints     -   points count.

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).

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
double mlprmserror(multilayerperceptron* network,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_state *_state)
{
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    double result;


    mlpproperties(network, &nin, &nout, &wcount, _state);
    result = ae_sqrt(2*mlperror(network, xy, npoints, _state)/(npoints*nout), _state);
    return result;
}


/*************************************************************************
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).
  
  -- ALGLIB --
     Copyright 09.08.2012 by Bochkanov Sergey
*************************************************************************/
double mlprmserrorsparse(multilayerperceptron* network,
     sparsematrix* xy,
     ae_int_t npoints,
     ae_state *_state)
{
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    double e;
    ae_int_t t0;
    ae_int_t t1;
    ae_int_t i;
    ae_int_t j;
    double result;


    ae_assert(npoints>=0, "MLPRMSErrorSparse: NPoints<0.", _state);
    if( npoints==0 )
    {
        result = 0;
        return result;
    }
    ae_assert(sparseiscrs(xy, _state), "MLPRMSErrorSparse: sparse matrix XY has not CRS format.", _state);
    
    /*
     * Check dataset correctness
     */
    t0 = 0;
    t1 = 0;
    mlpproperties(network, &nin, &nout, &wcount, _state);
    if( !mlpissoftmax(network, _state) )
    {
        while(sparseenumerate(xy, &t0, &t1, &i, &j, &e, _state))
        {
            ae_assert(ae_isfinite(e, _state), "MLPRMSErrorSparse: sparse matrix XY contains Infinite or NaN.", _state);
        }
    }
    else
    {
        while(sparseenumerate(xy, &t0, &t1, &i, &j, &e, _state))
        {
            if( j!=nin )
            {
                ae_assert(ae_isfinite(e, _state), "MLPRMSErrorSparse: sparse matrix XY contains Infinite or NaN.", _state);
            }
            else
            {
                ae_assert((ae_isfinite(e, _state)&&ae_round(e, _state)>=0)&&ae_round(e, _state)<nout, "MLPRMSErrorSparse: invalid sparse matrix XY(in classifier used nonexistent class number: either XY[.,NIn]<0 or XY[.,NIn]>=NClasses).", _state);
            }
        }
    }
    result = ae_sqrt(2*mlperrorsparse(network, xy, npoints, _state)/(npoints*nout), _state);
    return result;
}


/*************************************************************************
Average error on the test set.

INPUT PARAMETERS:
    Network     -   neural network;
    XY          -   training  set,  see  below  for  information  on   the
                    training set format;
    NPoints     -   points count.

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).

  -- ALGLIB --
     Copyright 11.03.2008 by Bochkanov Sergey
*************************************************************************/
double mlpavgerror(multilayerperceptron* network,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t j;
    ae_int_t k;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    double result;


    mlpproperties(network, &nin, &nout, &wcount, _state);
    result = 0;
    for(i=0; i<=npoints-1; i++)
    {
        ae_v_move(&network->x.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nin-1));
        mlpprocess(network, &network->x, &network->y, _state);
        if( mlpissoftmax(network, _state) )
        {
            
            /*
             * class labels
             */
            k = ae_round(xy->ptr.pp_double[i][nin], _state);
            for(j=0; j<=nout-1; j++)
            {
                if( j==k )
                {
                    result = result+ae_fabs(1-network->y.ptr.p_double[j], _state);
                }
                else
                {
                    result = result+ae_fabs(network->y.ptr.p_double[j], _state);
                }
            }
        }
        else
        {
            
            /*
             * real outputs
             */
            for(j=0; j<=nout-1; j++)
            {
                result = result+ae_fabs(xy->ptr.pp_double[i][nin+j]-network->y.ptr.p_double[j], _state);
            }
        }
    }
    result = result/(npoints*nout);
    return result;
}


/*************************************************************************
Average 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).
  
  -- ALGLIB --
     Copyright 09.08.2012 by Bochkanov Sergey
*************************************************************************/
double mlpavgerrorsparse(multilayerperceptron* network,
     sparsematrix* xy,
     ae_int_t npoints,
     ae_state *_state)
{
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    double e;
    ae_int_t t0;
    ae_int_t t1;
    ae_bool iscls;
    ae_int_t i;
    ae_int_t j;
    ae_int_t k;
    double result;


    ae_assert(npoints>=0, "MLPAvgErrorSparse: NPoints<0.", _state);
    if( npoints==0 )
    {
        result = 0;
        return result;
    }
    ae_assert(sparseiscrs(xy, _state), "MLPAvgErrorSparse: sparse matrix XY has not CRS format.", _state);
    
    /*
     * Check dataset correctness
     */
    t0 = 0;
    t1 = 0;
    mlpproperties(network, &nin, &nout, &wcount, _state);
    iscls = mlpissoftmax(network, _state);
    if( !iscls )
    {
        while(sparseenumerate(xy, &t0, &t1, &i, &j, &e, _state))
        {
            ae_assert(ae_isfinite(e, _state), "MLPAvgErrorSparse: sparse matrix XY contains Infinite or NaN.", _state);
        }
    }
    else
    {
        while(sparseenumerate(xy, &t0, &t1, &i, &j, &e, _state))
        {
            if( j!=nin )
            {
                ae_assert(ae_isfinite(e, _state), "MLPAvgErrorSparse: sparse matrix XY contains Infinite or NaN.", _state);
            }
            else
            {
                ae_assert((ae_isfinite(e, _state)&&ae_round(e, _state)>=0)&&ae_round(e, _state)<nout, "MLPAvgErrorSparse: invalid sparse matrix XY(in classifier used nonexistent class number: either XY[.,NIn]<0 or XY[.,NIn]>=NClasses).", _state);
            }
        }
    }
    result = 0;
    for(i=0; i<=npoints-1; i++)
    {
        sparsegetrow(xy, i, &network->x, _state);
        mlpprocess(network, &network->x, &network->y, _state);
        if( iscls )
        {
            
            /*
             * class labels
             */
            k = ae_round(network->x.ptr.p_double[nin], _state);
            for(j=0; j<=nout-1; j++)
            {
                if( j==k )
                {
                    result = result+ae_fabs(1-network->y.ptr.p_double[j], _state);
                }
                else
                {
                    result = result+ae_fabs(network->y.ptr.p_double[j], _state);
                }
            }
        }
        else
        {
            
            /*
             * real outputs
             */
            for(j=0; j<=nout-1; j++)
            {
                result = result+ae_fabs(network->x.ptr.p_double[nin+j]-network->y.ptr.p_double[j], _state);
            }
        }
    }
    result = result/(npoints*nout);
    return result;
}


/*************************************************************************
Average relative error on the test set.

INPUT PARAMETERS:
    Network     -   neural network;
    XY          -   training  set,  see  below  for  information  on   the
                    training set format;
    NPoints     -   points count.

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).

  -- ALGLIB --
     Copyright 11.03.2008 by Bochkanov Sergey
*************************************************************************/
double mlpavgrelerror(multilayerperceptron* network,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t j;
    ae_int_t k;
    ae_int_t lk;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    double result;


    mlpproperties(network, &nin, &nout, &wcount, _state);
    result = 0;
    k = 0;
    for(i=0; i<=npoints-1; i++)
    {
        ae_v_move(&network->x.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nin-1));
        mlpprocess(network, &network->x, &network->y, _state);
        if( mlpissoftmax(network, _state) )
        {
            
            /*
             * class labels
             */
            lk = ae_round(xy->ptr.pp_double[i][nin], _state);
            for(j=0; j<=nout-1; j++)
            {
                if( j==lk )
                {
                    result = result+ae_fabs(1-network->y.ptr.p_double[j], _state);
                    k = k+1;
                }
            }
        }
        else
        {
            
            /*
             * real outputs
             */
            for(j=0; j<=nout-1; j++)
            {
                if( ae_fp_neq(xy->ptr.pp_double[i][nin+j],0) )
                {
                    result = result+ae_fabs(xy->ptr.pp_double[i][nin+j]-network->y.ptr.p_double[j], _state)/ae_fabs(xy->ptr.pp_double[i][nin+j], _state);
                    k = k+1;
                }
            }
        }
    }
    if( k!=0 )
    {
        result = result/k;
    }
    return result;
}


/*************************************************************************
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).
  
  -- ALGLIB --
     Copyright 09.08.2012 by Bochkanov Sergey
*************************************************************************/
double mlpavgrelerrorsparse(multilayerperceptron* network,
     sparsematrix* xy,
     ae_int_t npoints,
     ae_state *_state)
{
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    double e;
    ae_int_t t0;
    ae_int_t t1;
    ae_bool iscls;
    ae_int_t i;
    ae_int_t j;
    ae_int_t k;
    ae_int_t lk;
    double result;


    ae_assert(npoints>=0, "MLPAvgRelErrorSparse: NPoints<0.", _state);
    if( npoints==0 )
    {
        result = 0;
        return result;
    }
    ae_assert(sparseiscrs(xy, _state), "MLPAvgRelErrorSparse: sparse matrix XY has not CRS format.", _state);
    
    /*
     * Check dataset correctness
     */
    t0 = 0;
    t1 = 0;
    mlpproperties(network, &nin, &nout, &wcount, _state);
    iscls = mlpissoftmax(network, _state);
    if( !iscls )
    {
        while(sparseenumerate(xy, &t0, &t1, &i, &j, &e, _state))
        {
            ae_assert(ae_isfinite(e, _state), "MLPAvgRelErrorSparse: sparse matrix XY contains Infinite or NaN.", _state);
        }
    }
    else
    {
        while(sparseenumerate(xy, &t0, &t1, &i, &j, &e, _state))
        {
            if( j!=nin )
            {
                ae_assert(ae_isfinite(e, _state), "MLPAvgRelErrorSparse: sparse matrix XY contains Infinite or NaN.", _state);
            }
            else
            {
                ae_assert((ae_isfinite(e, _state)&&ae_round(e, _state)>=0)&&ae_round(e, _state)<nout, "MLPAvgRelErrorSparse: invalid sparse matrix XY(in classifier used nonexistent class number: either XY[.,NIn]<0 or XY[.,NIn]>=NClasses).", _state);
            }
        }
    }
    result = 0;
    k = 0;
    for(i=0; i<=npoints-1; i++)
    {
        sparsegetrow(xy, i, &network->x, _state);
        mlpprocess(network, &network->x, &network->y, _state);
        if( iscls )
        {
            
            /*
             * class labels
             */
            lk = ae_round(network->x.ptr.p_double[nin], _state);
            for(j=0; j<=nout-1; j++)
            {
                if( j==lk )
                {
                    result = result+ae_fabs(1-network->y.ptr.p_double[j], _state);
                    k = k+1;
                }
            }
        }
        else
        {
            
            /*
             * real outputs
             */
            for(j=0; j<=nout-1; j++)
            {
                if( ae_fp_neq(network->x.ptr.p_double[nin+j],0) )
                {
                    result = result+ae_fabs(network->x.ptr.p_double[nin+j]-network->y.ptr.p_double[j], _state)/ae_fabs(network->x.ptr.p_double[nin+j], _state);
                    k = k+1;
                }
            }
        }
    }
    if( k!=0 )
    {
        result = result/k;
    }
    return result;
}


/*************************************************************************
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]
    
  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
void mlpgrad(multilayerperceptron* network,
     /* Real    */ ae_vector* x,
     /* Real    */ ae_vector* desiredy,
     double* e,
     /* Real    */ ae_vector* grad,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t nout;
    ae_int_t ntotal;

    *e = 0;

    
    /*
     * Alloc
     */
    rvectorsetlengthatleast(grad, network->structinfo.ptr.p_int[4], _state);
    
    /*
     * Prepare dError/dOut, internal structures
     */
    mlpprocess(network, x, &network->y, _state);
    nout = network->structinfo.ptr.p_int[2];
    ntotal = network->structinfo.ptr.p_int[3];
    *e = 0;
    for(i=0; i<=ntotal-1; i++)
    {
        network->derror.ptr.p_double[i] = 0;
    }
    for(i=0; i<=nout-1; i++)
    {
        network->derror.ptr.p_double[ntotal-nout+i] = network->y.ptr.p_double[i]-desiredy->ptr.p_double[i];
        *e = *e+ae_sqr(network->y.ptr.p_double[i]-desiredy->ptr.p_double[i], _state)/2;
    }
    
    /*
     * gradient
     */
    mlpbase_mlpinternalcalculategradient(network, &network->neurons, &network->weights, &network->derror, grad, ae_false, _state);
}


/*************************************************************************
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]

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
void mlpgradn(multilayerperceptron* network,
     /* Real    */ ae_vector* x,
     /* Real    */ ae_vector* desiredy,
     double* e,
     /* Real    */ ae_vector* grad,
     ae_state *_state)
{
    double s;
    ae_int_t i;
    ae_int_t nout;
    ae_int_t ntotal;

    *e = 0;

    
    /*
     * Alloc
     */
    rvectorsetlengthatleast(grad, network->structinfo.ptr.p_int[4], _state);
    
    /*
     * Prepare dError/dOut, internal structures
     */
    mlpprocess(network, x, &network->y, _state);
    nout = network->structinfo.ptr.p_int[2];
    ntotal = network->structinfo.ptr.p_int[3];
    for(i=0; i<=ntotal-1; i++)
    {
        network->derror.ptr.p_double[i] = 0;
    }
    *e = 0;
    if( network->structinfo.ptr.p_int[6]==0 )
    {
        
        /*
         * Regression network, least squares
         */
        for(i=0; i<=nout-1; i++)
        {
            network->derror.ptr.p_double[ntotal-nout+i] = network->y.ptr.p_double[i]-desiredy->ptr.p_double[i];
            *e = *e+ae_sqr(network->y.ptr.p_double[i]-desiredy->ptr.p_double[i], _state)/2;
        }
    }
    else
    {
        
        /*
         * Classification network, cross-entropy
         */
        s = 0;
        for(i=0; i<=nout-1; i++)
        {
            s = s+desiredy->ptr.p_double[i];
        }
        for(i=0; i<=nout-1; i++)
        {
            network->derror.ptr.p_double[ntotal-nout+i] = s*network->y.ptr.p_double[i]-desiredy->ptr.p_double[i];
            *e = *e+mlpbase_safecrossentropy(desiredy->ptr.p_double[i], network->y.ptr.p_double[i], _state);
        }
    }
    
    /*
     * gradient
     */
    mlpbase_mlpinternalcalculategradient(network, &network->neurons, &network->weights, &network->derror, grad, ae_true, _state);
}


/*************************************************************************
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]

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
void mlpgradbatch(multilayerperceptron* network,
     /* Real    */ ae_matrix* xy,
     ae_int_t ssize,
     double* e,
     /* Real    */ ae_vector* grad,
     ae_state *_state)
{
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    ae_int_t i;

    *e = 0;

    
    /*
     * Alloc
     */
    mlpproperties(network, &nin, &nout, &wcount, _state);
    rvectorsetlengthatleast(grad, wcount, _state);
    for(i=0; i<=wcount-1; i++)
    {
        grad->ptr.p_double[i] = 0;
    }
    *e = 0;
    i = 0;
    while(i<=ssize-1)
    {
        mlpbase_mlpchunkedgradient(network, xy, i, ae_minint(ssize, i+mlpbase_chunksize, _state)-i, e, grad, ae_false, _state);
        i = i+mlpbase_chunksize;
    }
}


/*************************************************************************
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      -   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]

  -- ALGLIB --
     Copyright 26.07.2012 by Bochkanov Sergey
*************************************************************************/
void mlpgradbatchsparse(multilayerperceptron* network,
     sparsematrix* xy,
     ae_int_t ssize,
     double* e,
     /* Real    */ ae_vector* grad,
     ae_state *_state)
{
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    ae_int_t csize;
    ae_int_t rowsize;
    ae_int_t i;
    ae_int_t j;

    *e = 0;

    
    /*
     * Alloc
     */
    mlpproperties(network, &nin, &nout, &wcount, _state);
    rvectorsetlengthatleast(grad, wcount, _state);
    if( mlpissoftmax(network, _state) )
    {
        rowsize = nin+1;
        rmatrixsetlengthatleast(&network->xy, mlpbase_chunksize, rowsize, _state);
    }
    else
    {
        rowsize = nin+nout;
        rmatrixsetlengthatleast(&network->xy, mlpbase_chunksize, rowsize, _state);
    }
    for(i=0; i<=wcount-1; i++)
    {
        grad->ptr.p_double[i] = 0;
    }
    *e = 0;
    i = 0;
    while(i<=ssize-1)
    {
        csize = ae_minint(ssize, i+mlpbase_chunksize, _state)-i;
        for(j=0; j<=csize-1; j++)
        {
            sparsegetrow(xy, i+j, &network->xyrow, _state);
            ae_v_move(&network->xy.ptr.pp_double[j][0], 1, &network->xyrow.ptr.p_double[0], 1, ae_v_len(0,rowsize-1));
        }
        mlpbase_mlpchunkedgradient(network, &network->xy, 0, csize, e, grad, ae_false, _state);
        i = i+mlpbase_chunksize;
    }
}


/*************************************************************************
Batch gradient calculation for a 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;
    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.
    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 MLPGradBatch function.

  -- ALGLIB --
     Copyright 26.07.2012 by Bochkanov Sergey
*************************************************************************/
void mlpgradbatchsubset(multilayerperceptron* network,
     /* Real    */ ae_matrix* xy,
     ae_int_t setsize,
     /* Integer */ ae_vector* idx,
     ae_int_t subsetsize,
     double* e,
     /* Real    */ ae_vector* grad,
     ae_state *_state)
{
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    ae_int_t csize;
    ae_int_t npoints;
    ae_int_t rowsize;
    ae_int_t i;
    ae_int_t j;

    *e = 0;

    ae_assert(setsize>=0, "MLPGradBatchSubset: SetSize<0", _state);
    if( subsetsize<0 )
    {
        mlpgradbatch(network, xy, setsize, e, grad, _state);
        return;
    }
    ae_assert(subsetsize<=idx->cnt, "MLPGradBatchSubset: SubsetSize>Length(Idx)", _state);
    npoints = setsize;
    for(i=0; i<=subsetsize-1; i++)
    {
        ae_assert(idx->ptr.p_int[i]>=0, "MLPGradBatchSubset: incorrect index of XY row(Idx[I]<0)", _state);
        ae_assert(idx->ptr.p_int[i]<=npoints-1, "MLPGradBatchSubset: incorrect index of XY row(Idx[I]>Rows(XY)-1)", _state);
    }
    
    /*
     * Alloc
     */
    mlpproperties(network, &nin, &nout, &wcount, _state);
    rvectorsetlengthatleast(grad, wcount, _state);
    if( mlpissoftmax(network, _state) )
    {
        rowsize = nin+1;
        rmatrixsetlengthatleast(&network->xy, mlpbase_chunksize, rowsize, _state);
    }
    else
    {
        rowsize = nin+nout;
        rmatrixsetlengthatleast(&network->xy, mlpbase_chunksize, rowsize, _state);
    }
    for(i=0; i<=wcount-1; i++)
    {
        grad->ptr.p_double[i] = 0;
    }
    *e = 0;
    i = 0;
    while(i<=subsetsize-1)
    {
        csize = ae_minint(subsetsize, i+mlpbase_chunksize, _state)-i;
        for(j=0; j<=csize-1; j++)
        {
            ae_v_move(&network->xy.ptr.pp_double[j][0], 1, &xy->ptr.pp_double[idx->ptr.p_int[i+j]][0], 1, ae_v_len(0,rowsize-1));
        }
        mlpbase_mlpchunkedgradient(network, &network->xy, 0, csize, e, grad, ae_false, _state);
        i = i+mlpbase_chunksize;
    }
}


/*************************************************************************
Batch gradient calculation for a set of inputs/outputs  for  a  subset  of
dataset given by boolean mask.

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.
    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.
    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.
    
  -- ALGLIB --
     Copyright 26.07.2012 by Bochkanov Sergey
*************************************************************************/
void mlpgradbatchsparsesubset(multilayerperceptron* network,
     sparsematrix* xy,
     ae_int_t setsize,
     /* Integer */ ae_vector* idx,
     ae_int_t subsetsize,
     double* e,
     /* Real    */ ae_vector* grad,
     ae_state *_state)
{
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    ae_int_t csize;
    ae_int_t npoints;
    ae_int_t rowsize;
    ae_int_t i;
    ae_int_t j;

    *e = 0;

    ae_assert(setsize>=0, "MLPGradBatchSparseSubset: SetSize<0", _state);
    if( subsetsize<0 )
    {
        mlpgradbatchsparse(network, xy, setsize, e, grad, _state);
        return;
    }
    ae_assert(subsetsize<=idx->cnt, "MLPGradBatchSparseSubset: SubsetSize>Length(Idx)", _state);
    npoints = setsize;
    for(i=0; i<=subsetsize-1; i++)
    {
        ae_assert(idx->ptr.p_int[i]>=0, "MLPGradBatchSparseSubset: incorrect index of XY row(Idx[I]<0)", _state);
        ae_assert(idx->ptr.p_int[i]<=npoints-1, "MLPGradBatchSparseSubset: incorrect index of XY row(Idx[I]>Rows(XY)-1)", _state);
    }
    
    /*
     * Alloc
     */
    mlpproperties(network, &nin, &nout, &wcount, _state);
    rvectorsetlengthatleast(grad, wcount, _state);
    if( mlpissoftmax(network, _state) )
    {
        rowsize = nin+1;
        rmatrixsetlengthatleast(&network->xy, mlpbase_chunksize, rowsize, _state);
    }
    else
    {
        rowsize = nin+nout;
        rmatrixsetlengthatleast(&network->xy, mlpbase_chunksize, rowsize, _state);
    }
    for(i=0; i<=wcount-1; i++)
    {
        grad->ptr.p_double[i] = 0;
    }
    *e = 0;
    i = 0;
    while(i<=subsetsize-1)
    {
        csize = ae_minint(subsetsize, i+mlpbase_chunksize, _state)-i;
        for(j=0; j<=csize-1; j++)
        {
            sparsegetrow(xy, idx->ptr.p_int[i+j], &network->xyrow, _state);
            ae_v_move(&network->xy.ptr.pp_double[j][0], 1, &network->xyrow.ptr.p_double[0], 1, ae_v_len(0,rowsize-1));
        }
        mlpbase_mlpchunkedgradient(network, &network->xy, 0, csize, e, grad, ae_false, _state);
        i = i+mlpbase_chunksize;
    }
}


/*************************************************************************
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]

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
void mlpgradnbatch(multilayerperceptron* network,
     /* Real    */ ae_matrix* xy,
     ae_int_t ssize,
     double* e,
     /* Real    */ ae_vector* grad,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;

    *e = 0;

    
    /*
     * Alloc
     */
    mlpproperties(network, &nin, &nout, &wcount, _state);
    rvectorsetlengthatleast(grad, wcount, _state);
    for(i=0; i<=wcount-1; i++)
    {
        grad->ptr.p_double[i] = 0;
    }
    *e = 0;
    i = 0;
    while(i<=ssize-1)
    {
        mlpbase_mlpchunkedgradient(network, xy, i, ae_minint(ssize, i+mlpbase_chunksize, _state)-i, e, grad, ae_true, _state);
        i = i+mlpbase_chunksize;
    }
}


/*************************************************************************
Batch Hessian calculation (natural error function) using R-algorithm.
Internal subroutine.

  -- ALGLIB --
     Copyright 26.01.2008 by Bochkanov Sergey.
     
     Hessian calculation based on R-algorithm described in
     "Fast Exact Multiplication by the Hessian",
     B. A. Pearlmutter,
     Neural Computation, 1994.
*************************************************************************/
void mlphessiannbatch(multilayerperceptron* network,
     /* Real    */ ae_matrix* xy,
     ae_int_t ssize,
     double* e,
     /* Real    */ ae_vector* grad,
     /* Real    */ ae_matrix* h,
     ae_state *_state)
{

    *e = 0;

    mlpbase_mlphessianbatchinternal(network, xy, ssize, ae_true, e, grad, h, _state);
}


/*************************************************************************
Batch Hessian calculation using R-algorithm.
Internal subroutine.

  -- ALGLIB --
     Copyright 26.01.2008 by Bochkanov Sergey.

     Hessian calculation based on R-algorithm described in
     "Fast Exact Multiplication by the Hessian",
     B. A. Pearlmutter,
     Neural Computation, 1994.
*************************************************************************/
void mlphessianbatch(multilayerperceptron* network,
     /* Real    */ ae_matrix* xy,
     ae_int_t ssize,
     double* e,
     /* Real    */ ae_vector* grad,
     /* Real    */ ae_matrix* h,
     ae_state *_state)
{

    *e = 0;

    mlpbase_mlphessianbatchinternal(network, xy, ssize, ae_false, e, grad, h, _state);
}


/*************************************************************************
Internal subroutine, shouldn't be called by user.
*************************************************************************/
void mlpinternalprocessvector(/* Integer */ ae_vector* structinfo,
     /* Real    */ ae_vector* weights,
     /* Real    */ ae_vector* columnmeans,
     /* Real    */ ae_vector* columnsigmas,
     /* Real    */ ae_vector* neurons,
     /* Real    */ ae_vector* dfdnet,
     /* Real    */ ae_vector* x,
     /* Real    */ ae_vector* y,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t n1;
    ae_int_t n2;
    ae_int_t w1;
    ae_int_t w2;
    ae_int_t ntotal;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t istart;
    ae_int_t offs;
    double net;
    double f;
    double df;
    double d2f;
    double mx;
    ae_bool perr;


    
    /*
     * Read network geometry
     */
    nin = structinfo->ptr.p_int[1];
    nout = structinfo->ptr.p_int[2];
    ntotal = structinfo->ptr.p_int[3];
    istart = structinfo->ptr.p_int[5];
    
    /*
     * Inputs standartisation and putting in the network
     */
    for(i=0; i<=nin-1; i++)
    {
        if( ae_fp_neq(columnsigmas->ptr.p_double[i],0) )
        {
            neurons->ptr.p_double[i] = (x->ptr.p_double[i]-columnmeans->ptr.p_double[i])/columnsigmas->ptr.p_double[i];
        }
        else
        {
            neurons->ptr.p_double[i] = x->ptr.p_double[i]-columnmeans->ptr.p_double[i];
        }
    }
    
    /*
     * Process network
     */
    for(i=0; i<=ntotal-1; i++)
    {
        offs = istart+i*mlpbase_nfieldwidth;
        if( structinfo->ptr.p_int[offs+0]>0||structinfo->ptr.p_int[offs+0]==-5 )
        {
            
            /*
             * Activation function
             */
            mlpactivationfunction(neurons->ptr.p_double[structinfo->ptr.p_int[offs+2]], structinfo->ptr.p_int[offs+0], &f, &df, &d2f, _state);
            neurons->ptr.p_double[i] = f;
            dfdnet->ptr.p_double[i] = df;
            continue;
        }
        if( structinfo->ptr.p_int[offs+0]==0 )
        {
            
            /*
             * Adaptive summator
             */
            n1 = structinfo->ptr.p_int[offs+2];
            n2 = n1+structinfo->ptr.p_int[offs+1]-1;
            w1 = structinfo->ptr.p_int[offs+3];
            w2 = w1+structinfo->ptr.p_int[offs+1]-1;
            net = ae_v_dotproduct(&weights->ptr.p_double[w1], 1, &neurons->ptr.p_double[n1], 1, ae_v_len(w1,w2));
            neurons->ptr.p_double[i] = net;
            dfdnet->ptr.p_double[i] = 1.0;
            continue;
        }
        if( structinfo->ptr.p_int[offs+0]<0 )
        {
            perr = ae_true;
            if( structinfo->ptr.p_int[offs+0]==-2 )
            {
                
                /*
                 * input neuron, left unchanged
                 */
                perr = ae_false;
            }
            if( structinfo->ptr.p_int[offs+0]==-3 )
            {
                
                /*
                 * "-1" neuron
                 */
                neurons->ptr.p_double[i] = -1;
                perr = ae_false;
            }
            if( structinfo->ptr.p_int[offs+0]==-4 )
            {
                
                /*
                 * "0" neuron
                 */
                neurons->ptr.p_double[i] = 0;
                perr = ae_false;
            }
            ae_assert(!perr, "MLPInternalProcessVector: internal error - unknown neuron type!", _state);
            continue;
        }
    }
    
    /*
     * Extract result
     */
    ae_v_move(&y->ptr.p_double[0], 1, &neurons->ptr.p_double[ntotal-nout], 1, ae_v_len(0,nout-1));
    
    /*
     * Softmax post-processing or standardisation if needed
     */
    ae_assert(structinfo->ptr.p_int[6]==0||structinfo->ptr.p_int[6]==1, "MLPInternalProcessVector: unknown normalization type!", _state);
    if( structinfo->ptr.p_int[6]==1 )
    {
        
        /*
         * Softmax
         */
        mx = y->ptr.p_double[0];
        for(i=1; i<=nout-1; i++)
        {
            mx = ae_maxreal(mx, y->ptr.p_double[i], _state);
        }
        net = 0;
        for(i=0; i<=nout-1; i++)
        {
            y->ptr.p_double[i] = ae_exp(y->ptr.p_double[i]-mx, _state);
            net = net+y->ptr.p_double[i];
        }
        for(i=0; i<=nout-1; i++)
        {
            y->ptr.p_double[i] = y->ptr.p_double[i]/net;
        }
    }
    else
    {
        
        /*
         * Standardisation
         */
        for(i=0; i<=nout-1; i++)
        {
            y->ptr.p_double[i] = y->ptr.p_double[i]*columnsigmas->ptr.p_double[nin+i]+columnmeans->ptr.p_double[nin+i];
        }
    }
}


/*************************************************************************
Serializer: allocation

  -- ALGLIB --
     Copyright 14.03.2011 by Bochkanov Sergey
*************************************************************************/
void mlpalloc(ae_serializer* s,
     multilayerperceptron* network,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t j;
    ae_int_t k;
    ae_int_t fkind;
    double threshold;
    double v0;
    double v1;
    ae_int_t nin;
    ae_int_t nout;


    nin = network->hllayersizes.ptr.p_int[0];
    nout = network->hllayersizes.ptr.p_int[network->hllayersizes.cnt-1];
    ae_serializer_alloc_entry(s);
    ae_serializer_alloc_entry(s);
    ae_serializer_alloc_entry(s);
    allocintegerarray(s, &network->hllayersizes, -1, _state);
    for(i=1; i<=network->hllayersizes.cnt-1; i++)
    {
        for(j=0; j<=network->hllayersizes.ptr.p_int[i]-1; j++)
        {
            mlpgetneuroninfo(network, i, j, &fkind, &threshold, _state);
            ae_serializer_alloc_entry(s);
            ae_serializer_alloc_entry(s);
            for(k=0; k<=network->hllayersizes.ptr.p_int[i-1]-1; k++)
            {
                ae_serializer_alloc_entry(s);
            }
        }
    }
    for(j=0; j<=nin-1; j++)
    {
        mlpgetinputscaling(network, j, &v0, &v1, _state);
        ae_serializer_alloc_entry(s);
        ae_serializer_alloc_entry(s);
    }
    for(j=0; j<=nout-1; j++)
    {
        mlpgetoutputscaling(network, j, &v0, &v1, _state);
        ae_serializer_alloc_entry(s);
        ae_serializer_alloc_entry(s);
    }
}


/*************************************************************************
Serializer: serialization

  -- ALGLIB --
     Copyright 14.03.2011 by Bochkanov Sergey
*************************************************************************/
void mlpserialize(ae_serializer* s,
     multilayerperceptron* network,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t j;
    ae_int_t k;
    ae_int_t fkind;
    double threshold;
    double v0;
    double v1;
    ae_int_t nin;
    ae_int_t nout;


    nin = network->hllayersizes.ptr.p_int[0];
    nout = network->hllayersizes.ptr.p_int[network->hllayersizes.cnt-1];
    ae_serializer_serialize_int(s, getmlpserializationcode(_state), _state);
    ae_serializer_serialize_int(s, mlpbase_mlpfirstversion, _state);
    ae_serializer_serialize_bool(s, mlpissoftmax(network, _state), _state);
    serializeintegerarray(s, &network->hllayersizes, -1, _state);
    for(i=1; i<=network->hllayersizes.cnt-1; i++)
    {
        for(j=0; j<=network->hllayersizes.ptr.p_int[i]-1; j++)
        {
            mlpgetneuroninfo(network, i, j, &fkind, &threshold, _state);
            ae_serializer_serialize_int(s, fkind, _state);
            ae_serializer_serialize_double(s, threshold, _state);
            for(k=0; k<=network->hllayersizes.ptr.p_int[i-1]-1; k++)
            {
                ae_serializer_serialize_double(s, mlpgetweight(network, i-1, k, i, j, _state), _state);
            }
        }
    }
    for(j=0; j<=nin-1; j++)
    {
        mlpgetinputscaling(network, j, &v0, &v1, _state);
        ae_serializer_serialize_double(s, v0, _state);
        ae_serializer_serialize_double(s, v1, _state);
    }
    for(j=0; j<=nout-1; j++)
    {
        mlpgetoutputscaling(network, j, &v0, &v1, _state);
        ae_serializer_serialize_double(s, v0, _state);
        ae_serializer_serialize_double(s, v1, _state);
    }
}


/*************************************************************************
Serializer: unserialization

  -- ALGLIB --
     Copyright 14.03.2011 by Bochkanov Sergey
*************************************************************************/
void mlpunserialize(ae_serializer* s,
     multilayerperceptron* network,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t i0;
    ae_int_t i1;
    ae_int_t i;
    ae_int_t j;
    ae_int_t k;
    ae_int_t fkind;
    double threshold;
    double v0;
    double v1;
    ae_int_t nin;
    ae_int_t nout;
    ae_bool issoftmax;
    ae_vector layersizes;

    ae_frame_make(_state, &_frame_block);
    _multilayerperceptron_clear(network);
    ae_vector_init(&layersizes, 0, DT_INT, _state, ae_true);

    
    /*
     * check correctness of header
     */
    ae_serializer_unserialize_int(s, &i0, _state);
    ae_assert(i0==getmlpserializationcode(_state), "MLPUnserialize: stream header corrupted", _state);
    ae_serializer_unserialize_int(s, &i1, _state);
    ae_assert(i1==mlpbase_mlpfirstversion, "MLPUnserialize: stream header corrupted", _state);
    
    /*
     * Create network
     */
    ae_serializer_unserialize_bool(s, &issoftmax, _state);
    unserializeintegerarray(s, &layersizes, _state);
    ae_assert((layersizes.cnt==2||layersizes.cnt==3)||layersizes.cnt==4, "MLPUnserialize: too many hidden layers!", _state);
    nin = layersizes.ptr.p_int[0];
    nout = layersizes.ptr.p_int[layersizes.cnt-1];
    if( layersizes.cnt==2 )
    {
        if( issoftmax )
        {
            mlpcreatec0(layersizes.ptr.p_int[0], layersizes.ptr.p_int[1], network, _state);
        }
        else
        {
            mlpcreate0(layersizes.ptr.p_int[0], layersizes.ptr.p_int[1], network, _state);
        }
    }
    if( layersizes.cnt==3 )
    {
        if( issoftmax )
        {
            mlpcreatec1(layersizes.ptr.p_int[0], layersizes.ptr.p_int[1], layersizes.ptr.p_int[2], network, _state);
        }
        else
        {
            mlpcreate1(layersizes.ptr.p_int[0], layersizes.ptr.p_int[1], layersizes.ptr.p_int[2], network, _state);
        }
    }
    if( layersizes.cnt==4 )
    {
        if( issoftmax )
        {
            mlpcreatec2(layersizes.ptr.p_int[0], layersizes.ptr.p_int[1], layersizes.ptr.p_int[2], layersizes.ptr.p_int[3], network, _state);
        }
        else
        {
            mlpcreate2(layersizes.ptr.p_int[0], layersizes.ptr.p_int[1], layersizes.ptr.p_int[2], layersizes.ptr.p_int[3], network, _state);
        }
    }
    
    /*
     * Load neurons and weights
     */
    for(i=1; i<=layersizes.cnt-1; i++)
    {
        for(j=0; j<=layersizes.ptr.p_int[i]-1; j++)
        {
            ae_serializer_unserialize_int(s, &fkind, _state);
            ae_serializer_unserialize_double(s, &threshold, _state);
            mlpsetneuroninfo(network, i, j, fkind, threshold, _state);
            for(k=0; k<=layersizes.ptr.p_int[i-1]-1; k++)
            {
                ae_serializer_unserialize_double(s, &v0, _state);
                mlpsetweight(network, i-1, k, i, j, v0, _state);
            }
        }
    }
    
    /*
     * Load standartizator
     */
    for(j=0; j<=nin-1; j++)
    {
        ae_serializer_unserialize_double(s, &v0, _state);
        ae_serializer_unserialize_double(s, &v1, _state);
        mlpsetinputscaling(network, j, v0, v1, _state);
    }
    for(j=0; j<=nout-1; j++)
    {
        ae_serializer_unserialize_double(s, &v0, _state);
        ae_serializer_unserialize_double(s, &v1, _state);
        mlpsetoutputscaling(network, j, v0, v1, _state);
    }
    ae_frame_leave(_state);
}


/*************************************************************************
Calculation of all types of errors.

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.

OUTPUT PARAMETERS:
    Rep     -   it contains all type of errors.

NOTE: when SubsetSize<0 is used full dataset by call MLPGradBatch function.

  -- ALGLIB --
     Copyright 04.09.2012 by Bochkanov Sergey
*************************************************************************/
void mlpallerrorssubset(multilayerperceptron* network,
     /* Real    */ ae_matrix* xy,
     ae_int_t setsize,
     /* Integer */ ae_vector* subset,
     ae_int_t subsetsize,
     modelerrors* rep,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_vector buf;
    ae_vector dy;
    ae_int_t rowsize;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    ae_bool iscls;
    ae_int_t i;

    ae_frame_make(_state, &_frame_block);
    _modelerrors_clear(rep);
    ae_vector_init(&buf, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&dy, 0, DT_REAL, _state, ae_true);

    ae_assert(setsize>=0, "MLPAllErrorsSubset: SetSize<0", _state);
    mlpproperties(network, &nin, &nout, &wcount, _state);
    iscls = mlpissoftmax(network, _state);
    
    /*
     * Estimate error using subset of training set.
     */
    rvectorsetlengthatleast(&network->x, nin, _state);
    if( iscls )
    {
        rowsize = nin+1;
        rvectorsetlengthatleast(&network->y, 1, _state);
        ae_vector_set_length(&dy, 1, _state);
        dserrallocate(nout, &buf, _state);
    }
    else
    {
        rowsize = nin+nout;
        rvectorsetlengthatleast(&network->y, nout, _state);
        ae_vector_set_length(&dy, nout, _state);
        dserrallocate(-nout, &buf, _state);
    }
    if( subsetsize<0 )
    {
        for(i=0; i<=setsize-1; i++)
        {
            ae_v_move(&network->x.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nin-1));
            mlpprocess(network, &network->x, &network->y, _state);
            if( iscls )
            {
                dy.ptr.p_double[0] = xy->ptr.pp_double[i][nin];
            }
            else
            {
                ae_v_move(&dy.ptr.p_double[0], 1, &xy->ptr.pp_double[i][nin], 1, ae_v_len(0,nout-1));
            }
            dserraccumulate(&buf, &network->y, &dy, _state);
        }
    }
    else
    {
        for(i=0; i<=subsetsize-1; i++)
        {
            ae_v_move(&network->x.ptr.p_double[0], 1, &xy->ptr.pp_double[subset->ptr.p_int[i]][0], 1, ae_v_len(0,nin-1));
            mlpprocess(network, &network->x, &network->y, _state);
            if( iscls )
            {
                dy.ptr.p_double[0] = xy->ptr.pp_double[subset->ptr.p_int[i]][nin];
            }
            else
            {
                ae_v_move(&dy.ptr.p_double[0], 1, &xy->ptr.pp_double[subset->ptr.p_int[i]][nin], 1, ae_v_len(0,nout-1));
            }
            dserraccumulate(&buf, &network->y, &dy, _state);
        }
    }
    dserrfinish(&buf, _state);
    rep->relclserror = buf.ptr.p_double[0];
    rep->avgce = buf.ptr.p_double[1];
    rep->rmserror = buf.ptr.p_double[2];
    rep->avgerror = buf.ptr.p_double[3];
    rep->avgrelerror = buf.ptr.p_double[4];
    ae_frame_leave(_state);
}


/*************************************************************************
Calculation of all types of errors.

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.

OUTPUT PARAMETERS:
    Rep     -   it contains all type of errors.

NOTE: when SubsetSize<0 is used full dataset by call MLPGradBatch function.

  -- ALGLIB --
     Copyright 04.09.2012 by Bochkanov Sergey
*************************************************************************/
void mlpallerrorssparsesubset(multilayerperceptron* network,
     sparsematrix* xy,
     ae_int_t setsize,
     /* Integer */ ae_vector* subset,
     ae_int_t subsetsize,
     modelerrors* rep,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_vector buf;
    ae_vector dy;
    ae_int_t rowsize;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    ae_bool iscls;
    ae_int_t i;

    ae_frame_make(_state, &_frame_block);
    _modelerrors_clear(rep);
    ae_vector_init(&buf, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&dy, 0, DT_REAL, _state, ae_true);

    ae_assert(setsize>=0, "MLPAllErrorsSparseSubset: SetSize<0", _state);
    mlpproperties(network, &nin, &nout, &wcount, _state);
    iscls = mlpissoftmax(network, _state);
    
    /*
     * Estimate error using subset of training set.
     */
    rvectorsetlengthatleast(&network->x, nin, _state);
    if( iscls )
    {
        rowsize = nin+1;
        rvectorsetlengthatleast(&network->y, 1, _state);
        ae_vector_set_length(&dy, 1, _state);
        dserrallocate(nout, &buf, _state);
    }
    else
    {
        rowsize = nin+nout;
        rvectorsetlengthatleast(&network->y, nout, _state);
        ae_vector_set_length(&dy, nout, _state);
        dserrallocate(-nout, &buf, _state);
    }
    if( subsetsize<0 )
    {
        for(i=0; i<=setsize-1; i++)
        {
            sparsegetrow(xy, i, &network->xyrow, _state);
            ae_v_move(&network->x.ptr.p_double[0], 1, &network->xyrow.ptr.p_double[0], 1, ae_v_len(0,nin-1));
            mlpprocess(network, &network->x, &network->y, _state);
            if( iscls )
            {
                dy.ptr.p_double[0] = network->xyrow.ptr.p_double[nin];
            }
            else
            {
                ae_v_move(&dy.ptr.p_double[0], 1, &network->xyrow.ptr.p_double[nin], 1, ae_v_len(0,nout-1));
            }
            dserraccumulate(&buf, &network->y, &dy, _state);
        }
    }
    else
    {
        for(i=0; i<=subsetsize-1; i++)
        {
            sparsegetrow(xy, subset->ptr.p_int[i], &network->xyrow, _state);
            ae_v_move(&network->x.ptr.p_double[0], 1, &network->xyrow.ptr.p_double[0], 1, ae_v_len(0,nin-1));
            mlpprocess(network, &network->x, &network->y, _state);
            if( iscls )
            {
                dy.ptr.p_double[0] = network->xyrow.ptr.p_double[nin];
            }
            else
            {
                ae_v_move(&dy.ptr.p_double[0], 1, &network->xyrow.ptr.p_double[nin], 1, ae_v_len(0,nout-1));
            }
            dserraccumulate(&buf, &network->y, &dy, _state);
        }
    }
    dserrfinish(&buf, _state);
    rep->relclserror = buf.ptr.p_double[0];
    rep->avgce = buf.ptr.p_double[1];
    rep->rmserror = buf.ptr.p_double[2];
    rep->avgerror = buf.ptr.p_double[3];
    rep->avgrelerror = buf.ptr.p_double[4];
    ae_frame_leave(_state);
}


/*************************************************************************
Error of the neural network on 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.

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).

  -- ALGLIB --
     Copyright 04.09.2012 by Bochkanov Sergey
*************************************************************************/
double mlperrorsubset(multilayerperceptron* network,
     /* Real    */ ae_matrix* xy,
     ae_int_t setsize,
     /* Integer */ ae_vector* subset,
     ae_int_t subsetsize,
     ae_state *_state)
{
    ae_int_t rowsize;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    ae_int_t i;
    double result;


    ae_assert(setsize>=0, "MLPErrorSubset: SetSize<0", _state);
    mlpproperties(network, &nin, &nout, &wcount, _state);
    
    /*
     * Estimate error using subset of training set.
     */
    if( mlpissoftmax(network, _state) )
    {
        rowsize = nin+1;
    }
    else
    {
        rowsize = nin+nout;
    }
    rmatrixsetlengthatleast(&network->xy, 1, rowsize, _state);
    if( subsetsize<0 )
    {
        result = mlperror(network, xy, setsize, _state);
    }
    else
    {
        result = 0;
        for(i=0; i<=subsetsize-1; i++)
        {
            ae_v_move(&network->xy.ptr.pp_double[0][0], 1, &xy->ptr.pp_double[subset->ptr.p_int[i]][0], 1, ae_v_len(0,rowsize-1));
            result = result+mlperror(network, &network->xy, 1, _state);
        }
    }
    return result;
}


/*************************************************************************
Error of the neural network on 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.

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).

  -- ALGLIB --
     Copyright 04.09.2012 by Bochkanov Sergey
*************************************************************************/
double mlperrorsparsesubset(multilayerperceptron* network,
     sparsematrix* xy,
     ae_int_t setsize,
     /* Integer */ ae_vector* subset,
     ae_int_t subsetsize,
     ae_state *_state)
{
    ae_int_t rowsize;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    double e;
    ae_int_t t0;
    ae_int_t t1;
    ae_bool iscls;
    ae_int_t i;
    ae_int_t j;
    double result;


    ae_assert(setsize>=0, "MLPErrorSparseSubset: SetSize<0.", _state);
    ae_assert(sparseiscrs(xy, _state), "MLPErrorSparseSubset: sparse matrix XY has not CRS format.", _state);
    
    /*
     * Check dataset correctness
     */
    t0 = 0;
    t1 = 0;
    mlpproperties(network, &nin, &nout, &wcount, _state);
    iscls = mlpissoftmax(network, _state);
    if( !iscls )
    {
        while(sparseenumerate(xy, &t0, &t1, &i, &j, &e, _state))
        {
            ae_assert(ae_isfinite(e, _state), "MLPErrorSparseSubset: sparse matrix XY contains Infinite or NaN.", _state);
        }
    }
    else
    {
        while(sparseenumerate(xy, &t0, &t1, &i, &j, &e, _state))
        {
            if( j!=nin )
            {
                ae_assert(ae_isfinite(e, _state), "MLPErrorSparseSubset: sparse matrix XY contains Infinite or NaN.", _state);
            }
            else
            {
                ae_assert((ae_isfinite(e, _state)&&ae_round(e, _state)>=0)&&ae_round(e, _state)<nout, "MLPErrorSparseSubset: invalid sparse matrix XY(in classifier used nonexistent class number: either XY[.,NIn]<0 or XY[.,NIn]>=NClasses).", _state);
            }
        }
    }
    
    /*
     * Estimate error using subset of training set.
     */
    if( iscls )
    {
        rowsize = nin+1;
    }
    else
    {
        rowsize = nin+nout;
    }
    rmatrixsetlengthatleast(&network->xy, 1, rowsize, _state);
    if( subsetsize<0 )
    {
        result = mlperrorsparse(network, xy, setsize, _state);
    }
    else
    {
        result = 0;
        for(i=0; i<=subsetsize-1; i++)
        {
            sparsegetrow(xy, subset->ptr.p_int[i], &network->xyrow, _state);
            ae_v_move(&network->xy.ptr.pp_double[0][0], 1, &network->xyrow.ptr.p_double[0], 1, ae_v_len(0,rowsize-1));
            result = result+mlperror(network, &network->xy, 1, _state);
        }
    }
    return result;
}


/*************************************************************************
Internal subroutine: adding new input layer to network
*************************************************************************/
static void mlpbase_addinputlayer(ae_int_t ncount,
     /* Integer */ ae_vector* lsizes,
     /* Integer */ ae_vector* ltypes,
     /* Integer */ ae_vector* lconnfirst,
     /* Integer */ ae_vector* lconnlast,
     ae_int_t* lastproc,
     ae_state *_state)
{


    lsizes->ptr.p_int[0] = ncount;
    ltypes->ptr.p_int[0] = -2;
    lconnfirst->ptr.p_int[0] = 0;
    lconnlast->ptr.p_int[0] = 0;
    *lastproc = 0;
}


/*************************************************************************
Internal subroutine: adding new summator layer to network
*************************************************************************/
static void mlpbase_addbiasedsummatorlayer(ae_int_t ncount,
     /* Integer */ ae_vector* lsizes,
     /* Integer */ ae_vector* ltypes,
     /* Integer */ ae_vector* lconnfirst,
     /* Integer */ ae_vector* lconnlast,
     ae_int_t* lastproc,
     ae_state *_state)
{


    lsizes->ptr.p_int[*lastproc+1] = 1;
    ltypes->ptr.p_int[*lastproc+1] = -3;
    lconnfirst->ptr.p_int[*lastproc+1] = 0;
    lconnlast->ptr.p_int[*lastproc+1] = 0;
    lsizes->ptr.p_int[*lastproc+2] = ncount;
    ltypes->ptr.p_int[*lastproc+2] = 0;
    lconnfirst->ptr.p_int[*lastproc+2] = *lastproc;
    lconnlast->ptr.p_int[*lastproc+2] = *lastproc+1;
    *lastproc = *lastproc+2;
}


/*************************************************************************
Internal subroutine: adding new summator layer to network
*************************************************************************/
static void mlpbase_addactivationlayer(ae_int_t functype,
     /* Integer */ ae_vector* lsizes,
     /* Integer */ ae_vector* ltypes,
     /* Integer */ ae_vector* lconnfirst,
     /* Integer */ ae_vector* lconnlast,
     ae_int_t* lastproc,
     ae_state *_state)
{


    ae_assert(functype>0||functype==-5, "AddActivationLayer: incorrect function type", _state);
    lsizes->ptr.p_int[*lastproc+1] = lsizes->ptr.p_int[*lastproc];
    ltypes->ptr.p_int[*lastproc+1] = functype;
    lconnfirst->ptr.p_int[*lastproc+1] = *lastproc;
    lconnlast->ptr.p_int[*lastproc+1] = *lastproc;
    *lastproc = *lastproc+1;
}


/*************************************************************************
Internal subroutine: adding new zero layer to network
*************************************************************************/
static void mlpbase_addzerolayer(/* Integer */ ae_vector* lsizes,
     /* Integer */ ae_vector* ltypes,
     /* Integer */ ae_vector* lconnfirst,
     /* Integer */ ae_vector* lconnlast,
     ae_int_t* lastproc,
     ae_state *_state)
{


    lsizes->ptr.p_int[*lastproc+1] = 1;
    ltypes->ptr.p_int[*lastproc+1] = -4;
    lconnfirst->ptr.p_int[*lastproc+1] = 0;
    lconnlast->ptr.p_int[*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.
*************************************************************************/
static void mlpbase_hladdinputlayer(multilayerperceptron* network,
     ae_int_t* connidx,
     ae_int_t* neuroidx,
     ae_int_t* structinfoidx,
     ae_int_t nin,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t offs;


    offs = mlpbase_hlnfieldwidth*(*neuroidx);
    for(i=0; i<=nin-1; i++)
    {
        network->hlneurons.ptr.p_int[offs+0] = 0;
        network->hlneurons.ptr.p_int[offs+1] = i;
        network->hlneurons.ptr.p_int[offs+2] = -1;
        network->hlneurons.ptr.p_int[offs+3] = -1;
        offs = offs+mlpbase_hlnfieldwidth;
    }
    *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.
*************************************************************************/
static void mlpbase_hladdoutputlayer(multilayerperceptron* network,
     ae_int_t* connidx,
     ae_int_t* neuroidx,
     ae_int_t* structinfoidx,
     ae_int_t* weightsidx,
     ae_int_t k,
     ae_int_t nprev,
     ae_int_t nout,
     ae_bool iscls,
     ae_bool islinearout,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t j;
    ae_int_t neurooffs;
    ae_int_t connoffs;


    ae_assert((iscls&&islinearout)||!iscls, "HLAddOutputLayer: internal error", _state);
    neurooffs = mlpbase_hlnfieldwidth*(*neuroidx);
    connoffs = mlpbase_hlconnfieldwidth*(*connidx);
    if( !iscls )
    {
        
        /*
         * Regression network
         */
        for(i=0; i<=nout-1; i++)
        {
            network->hlneurons.ptr.p_int[neurooffs+0] = k;
            network->hlneurons.ptr.p_int[neurooffs+1] = i;
            network->hlneurons.ptr.p_int[neurooffs+2] = *structinfoidx+1+nout+i;
            network->hlneurons.ptr.p_int[neurooffs+3] = *weightsidx+nprev+(nprev+1)*i;
            neurooffs = neurooffs+mlpbase_hlnfieldwidth;
        }
        for(i=0; i<=nprev-1; i++)
        {
            for(j=0; j<=nout-1; j++)
            {
                network->hlconnections.ptr.p_int[connoffs+0] = k-1;
                network->hlconnections.ptr.p_int[connoffs+1] = i;
                network->hlconnections.ptr.p_int[connoffs+2] = k;
                network->hlconnections.ptr.p_int[connoffs+3] = j;
                network->hlconnections.ptr.p_int[connoffs+4] = *weightsidx+i+j*(nprev+1);
                connoffs = connoffs+mlpbase_hlconnfieldwidth;
            }
        }
        *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-2; i++)
        {
            network->hlneurons.ptr.p_int[neurooffs+0] = k;
            network->hlneurons.ptr.p_int[neurooffs+1] = i;
            network->hlneurons.ptr.p_int[neurooffs+2] = -1;
            network->hlneurons.ptr.p_int[neurooffs+3] = *weightsidx+nprev+(nprev+1)*i;
            neurooffs = neurooffs+mlpbase_hlnfieldwidth;
        }
        network->hlneurons.ptr.p_int[neurooffs+0] = k;
        network->hlneurons.ptr.p_int[neurooffs+1] = i;
        network->hlneurons.ptr.p_int[neurooffs+2] = -1;
        network->hlneurons.ptr.p_int[neurooffs+3] = -1;
        for(i=0; i<=nprev-1; i++)
        {
            for(j=0; j<=nout-2; j++)
            {
                network->hlconnections.ptr.p_int[connoffs+0] = k-1;
                network->hlconnections.ptr.p_int[connoffs+1] = i;
                network->hlconnections.ptr.p_int[connoffs+2] = k;
                network->hlconnections.ptr.p_int[connoffs+3] = j;
                network->hlconnections.ptr.p_int[connoffs+4] = *weightsidx+i+j*(nprev+1);
                connoffs = connoffs+mlpbase_hlconnfieldwidth;
            }
        }
        *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.
*************************************************************************/
static void mlpbase_hladdhiddenlayer(multilayerperceptron* network,
     ae_int_t* connidx,
     ae_int_t* neuroidx,
     ae_int_t* structinfoidx,
     ae_int_t* weightsidx,
     ae_int_t k,
     ae_int_t nprev,
     ae_int_t ncur,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t j;
    ae_int_t neurooffs;
    ae_int_t connoffs;


    neurooffs = mlpbase_hlnfieldwidth*(*neuroidx);
    connoffs = mlpbase_hlconnfieldwidth*(*connidx);
    for(i=0; i<=ncur-1; i++)
    {
        network->hlneurons.ptr.p_int[neurooffs+0] = k;
        network->hlneurons.ptr.p_int[neurooffs+1] = i;
        network->hlneurons.ptr.p_int[neurooffs+2] = *structinfoidx+1+ncur+i;
        network->hlneurons.ptr.p_int[neurooffs+3] = *weightsidx+nprev+(nprev+1)*i;
        neurooffs = neurooffs+mlpbase_hlnfieldwidth;
    }
    for(i=0; i<=nprev-1; i++)
    {
        for(j=0; j<=ncur-1; j++)
        {
            network->hlconnections.ptr.p_int[connoffs+0] = k-1;
            network->hlconnections.ptr.p_int[connoffs+1] = i;
            network->hlconnections.ptr.p_int[connoffs+2] = k;
            network->hlconnections.ptr.p_int[connoffs+3] = j;
            network->hlconnections.ptr.p_int[connoffs+4] = *weightsidx+i+j*(nprev+1);
            connoffs = connoffs+mlpbase_hlconnfieldwidth;
        }
    }
    *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 exists any
    activation function      |
    ...                      |
    activation function     /
    
    "-1" neuron            \
    biased summator         | output layer:
    ...                     |
    biased summator         | * we have NOut summators/activators for regression networks
    activation function     | * we have only NOut-1 summators and no activators for classifiers
    ...                     | * we have "0" neuron only when we have classifier
    activation function     |
    "0" neuron              /


  -- ALGLIB --
     Copyright 30.03.2008 by Bochkanov Sergey
*************************************************************************/
static void mlpbase_fillhighlevelinformation(multilayerperceptron* network,
     ae_int_t nin,
     ae_int_t nhid1,
     ae_int_t nhid2,
     ae_int_t nout,
     ae_bool iscls,
     ae_bool islinearout,
     ae_state *_state)
{
    ae_int_t idxweights;
    ae_int_t idxstruct;
    ae_int_t idxneuro;
    ae_int_t idxconn;


    ae_assert((iscls&&islinearout)||!iscls, "FillHighLevelInformation: internal error", _state);
    
    /*
     * Preparations common to all types of networks
     */
    idxweights = 0;
    idxneuro = 0;
    idxstruct = 0;
    idxconn = 0;
    network->hlnetworktype = 0;
    
    /*
     * network without hidden layers
     */
    if( nhid1==0 )
    {
        ae_vector_set_length(&network->hllayersizes, 2, _state);
        network->hllayersizes.ptr.p_int[0] = nin;
        network->hllayersizes.ptr.p_int[1] = nout;
        if( !iscls )
        {
            ae_vector_set_length(&network->hlconnections, mlpbase_hlconnfieldwidth*nin*nout, _state);
            ae_vector_set_length(&network->hlneurons, mlpbase_hlnfieldwidth*(nin+nout), _state);
            network->hlnormtype = 0;
        }
        else
        {
            ae_vector_set_length(&network->hlconnections, mlpbase_hlconnfieldwidth*nin*(nout-1), _state);
            ae_vector_set_length(&network->hlneurons, mlpbase_hlnfieldwidth*(nin+nout), _state);
            network->hlnormtype = 1;
        }
        mlpbase_hladdinputlayer(network, &idxconn, &idxneuro, &idxstruct, nin, _state);
        mlpbase_hladdoutputlayer(network, &idxconn, &idxneuro, &idxstruct, &idxweights, 1, nin, nout, iscls, islinearout, _state);
        return;
    }
    
    /*
     * network with one hidden layers
     */
    if( nhid2==0 )
    {
        ae_vector_set_length(&network->hllayersizes, 3, _state);
        network->hllayersizes.ptr.p_int[0] = nin;
        network->hllayersizes.ptr.p_int[1] = nhid1;
        network->hllayersizes.ptr.p_int[2] = nout;
        if( !iscls )
        {
            ae_vector_set_length(&network->hlconnections, mlpbase_hlconnfieldwidth*(nin*nhid1+nhid1*nout), _state);
            ae_vector_set_length(&network->hlneurons, mlpbase_hlnfieldwidth*(nin+nhid1+nout), _state);
            network->hlnormtype = 0;
        }
        else
        {
            ae_vector_set_length(&network->hlconnections, mlpbase_hlconnfieldwidth*(nin*nhid1+nhid1*(nout-1)), _state);
            ae_vector_set_length(&network->hlneurons, mlpbase_hlnfieldwidth*(nin+nhid1+nout), _state);
            network->hlnormtype = 1;
        }
        mlpbase_hladdinputlayer(network, &idxconn, &idxneuro, &idxstruct, nin, _state);
        mlpbase_hladdhiddenlayer(network, &idxconn, &idxneuro, &idxstruct, &idxweights, 1, nin, nhid1, _state);
        mlpbase_hladdoutputlayer(network, &idxconn, &idxneuro, &idxstruct, &idxweights, 2, nhid1, nout, iscls, islinearout, _state);
        return;
    }
    
    /*
     * Two hidden layers
     */
    ae_vector_set_length(&network->hllayersizes, 4, _state);
    network->hllayersizes.ptr.p_int[0] = nin;
    network->hllayersizes.ptr.p_int[1] = nhid1;
    network->hllayersizes.ptr.p_int[2] = nhid2;
    network->hllayersizes.ptr.p_int[3] = nout;
    if( !iscls )
    {
        ae_vector_set_length(&network->hlconnections, mlpbase_hlconnfieldwidth*(nin*nhid1+nhid1*nhid2+nhid2*nout), _state);
        ae_vector_set_length(&network->hlneurons, mlpbase_hlnfieldwidth*(nin+nhid1+nhid2+nout), _state);
        network->hlnormtype = 0;
    }
    else
    {
        ae_vector_set_length(&network->hlconnections, mlpbase_hlconnfieldwidth*(nin*nhid1+nhid1*nhid2+nhid2*(nout-1)), _state);
        ae_vector_set_length(&network->hlneurons, mlpbase_hlnfieldwidth*(nin+nhid1+nhid2+nout), _state);
        network->hlnormtype = 1;
    }
    mlpbase_hladdinputlayer(network, &idxconn, &idxneuro, &idxstruct, nin, _state);
    mlpbase_hladdhiddenlayer(network, &idxconn, &idxneuro, &idxstruct, &idxweights, 1, nin, nhid1, _state);
    mlpbase_hladdhiddenlayer(network, &idxconn, &idxneuro, &idxstruct, &idxweights, 2, nhid1, nhid2, _state);
    mlpbase_hladdoutputlayer(network, &idxconn, &idxneuro, &idxstruct, &idxweights, 3, nhid2, nout, iscls, islinearout, _state);
}


/*************************************************************************
Internal subroutine.

  -- ALGLIB --
     Copyright 04.11.2007 by Bochkanov Sergey
*************************************************************************/
static void mlpbase_mlpcreate(ae_int_t nin,
     ae_int_t nout,
     /* Integer */ ae_vector* lsizes,
     /* Integer */ ae_vector* ltypes,
     /* Integer */ ae_vector* lconnfirst,
     /* Integer */ ae_vector* lconnlast,
     ae_int_t layerscount,
     ae_bool isclsnet,
     multilayerperceptron* network,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t i;
    ae_int_t j;
    ae_int_t ssize;
    ae_int_t ntotal;
    ae_int_t wcount;
    ae_int_t offs;
    ae_int_t nprocessed;
    ae_int_t wallocated;
    ae_vector localtemp;
    ae_vector lnfirst;
    ae_vector lnsyn;

    ae_frame_make(_state, &_frame_block);
    _multilayerperceptron_clear(network);
    ae_vector_init(&localtemp, 0, DT_INT, _state, ae_true);
    ae_vector_init(&lnfirst, 0, DT_INT, _state, ae_true);
    ae_vector_init(&lnsyn, 0, DT_INT, _state, ae_true);

    
    /*
     * Check
     */
    ae_assert(layerscount>0, "MLPCreate: wrong parameters!", _state);
    ae_assert(ltypes->ptr.p_int[0]==-2, "MLPCreate: wrong LTypes[0] (must be -2)!", _state);
    for(i=0; i<=layerscount-1; i++)
    {
        ae_assert(lsizes->ptr.p_int[i]>0, "MLPCreate: wrong LSizes!", _state);
        ae_assert(lconnfirst->ptr.p_int[i]>=0&&(lconnfirst->ptr.p_int[i]<i||i==0), "MLPCreate: wrong LConnFirst!", _state);
        ae_assert(lconnlast->ptr.p_int[i]>=lconnfirst->ptr.p_int[i]&&(lconnlast->ptr.p_int[i]<i||i==0), "MLPCreate: wrong LConnLast!", _state);
    }
    
    /*
     * Build network geometry
     */
    ae_vector_set_length(&lnfirst, layerscount-1+1, _state);
    ae_vector_set_length(&lnsyn, layerscount-1+1, _state);
    ntotal = 0;
    wcount = 0;
    for(i=0; i<=layerscount-1; i++)
    {
        
        /*
         * Analyze connections.
         * This code must throw an assertion in case of unknown LTypes[I]
         */
        lnsyn.ptr.p_int[i] = -1;
        if( ltypes->ptr.p_int[i]>=0||ltypes->ptr.p_int[i]==-5 )
        {
            lnsyn.ptr.p_int[i] = 0;
            for(j=lconnfirst->ptr.p_int[i]; j<=lconnlast->ptr.p_int[i]; j++)
            {
                lnsyn.ptr.p_int[i] = lnsyn.ptr.p_int[i]+lsizes->ptr.p_int[j];
            }
        }
        else
        {
            if( (ltypes->ptr.p_int[i]==-2||ltypes->ptr.p_int[i]==-3)||ltypes->ptr.p_int[i]==-4 )
            {
                lnsyn.ptr.p_int[i] = 0;
            }
        }
        ae_assert(lnsyn.ptr.p_int[i]>=0, "MLPCreate: internal error #0!", _state);
        
        /*
         * Other info
         */
        lnfirst.ptr.p_int[i] = ntotal;
        ntotal = ntotal+lsizes->ptr.p_int[i];
        if( ltypes->ptr.p_int[i]==0 )
        {
            wcount = wcount+lnsyn.ptr.p_int[i]*lsizes->ptr.p_int[i];
        }
    }
    ssize = 7+ntotal*mlpbase_nfieldwidth;
    
    /*
     * Allocate
     */
    ae_vector_set_length(&network->structinfo, ssize-1+1, _state);
    ae_vector_set_length(&network->weights, wcount-1+1, _state);
    if( isclsnet )
    {
        ae_vector_set_length(&network->columnmeans, nin-1+1, _state);
        ae_vector_set_length(&network->columnsigmas, nin-1+1, _state);
    }
    else
    {
        ae_vector_set_length(&network->columnmeans, nin+nout-1+1, _state);
        ae_vector_set_length(&network->columnsigmas, nin+nout-1+1, _state);
    }
    ae_vector_set_length(&network->neurons, ntotal-1+1, _state);
    ae_matrix_set_length(&network->chunks, 3*ntotal+1, mlpbase_chunksize-1+1, _state);
    ae_vector_set_length(&network->nwbuf, ae_maxint(wcount, 2*nout, _state)-1+1, _state);
    ae_vector_set_length(&network->integerbuf, 3+1, _state);
    ae_vector_set_length(&network->dfdnet, ntotal-1+1, _state);
    ae_vector_set_length(&network->x, nin-1+1, _state);
    ae_vector_set_length(&network->y, nout-1+1, _state);
    ae_vector_set_length(&network->derror, ntotal-1+1, _state);
    
    /*
     * Fill structure: global info
     */
    network->structinfo.ptr.p_int[0] = ssize;
    network->structinfo.ptr.p_int[1] = nin;
    network->structinfo.ptr.p_int[2] = nout;
    network->structinfo.ptr.p_int[3] = ntotal;
    network->structinfo.ptr.p_int[4] = wcount;
    network->structinfo.ptr.p_int[5] = 7;
    if( isclsnet )
    {
        network->structinfo.ptr.p_int[6] = 1;
    }
    else
    {
        network->structinfo.ptr.p_int[6] = 0;
    }
    
    /*
     * Fill structure: neuron connections
     */
    nprocessed = 0;
    wallocated = 0;
    for(i=0; i<=layerscount-1; i++)
    {
        for(j=0; j<=lsizes->ptr.p_int[i]-1; j++)
        {
            offs = network->structinfo.ptr.p_int[5]+nprocessed*mlpbase_nfieldwidth;
            network->structinfo.ptr.p_int[offs+0] = ltypes->ptr.p_int[i];
            if( ltypes->ptr.p_int[i]==0 )
            {
                
                /*
                 * Adaptive summator:
                 * * connections with weights to previous neurons
                 */
                network->structinfo.ptr.p_int[offs+1] = lnsyn.ptr.p_int[i];
                network->structinfo.ptr.p_int[offs+2] = lnfirst.ptr.p_int[lconnfirst->ptr.p_int[i]];
                network->structinfo.ptr.p_int[offs+3] = wallocated;
                wallocated = wallocated+lnsyn.ptr.p_int[i];
                nprocessed = nprocessed+1;
            }
            if( ltypes->ptr.p_int[i]>0||ltypes->ptr.p_int[i]==-5 )
            {
                
                /*
                 * Activation layer:
                 * * each neuron connected to one (only one) of previous neurons.
                 * * no weights
                 */
                network->structinfo.ptr.p_int[offs+1] = 1;
                network->structinfo.ptr.p_int[offs+2] = lnfirst.ptr.p_int[lconnfirst->ptr.p_int[i]]+j;
                network->structinfo.ptr.p_int[offs+3] = -1;
                nprocessed = nprocessed+1;
            }
            if( (ltypes->ptr.p_int[i]==-2||ltypes->ptr.p_int[i]==-3)||ltypes->ptr.p_int[i]==-4 )
            {
                nprocessed = nprocessed+1;
            }
        }
    }
    ae_assert(wallocated==wcount, "MLPCreate: internal error #1!", _state);
    ae_assert(nprocessed==ntotal, "MLPCreate: internal error #2!", _state);
    
    /*
     * Fill weights by small random values
     * Initialize means and sigmas
     */
    for(i=0; i<=wcount-1; i++)
    {
        network->weights.ptr.p_double[i] = ae_randomreal(_state)-0.5;
    }
    for(i=0; i<=nin-1; i++)
    {
        network->columnmeans.ptr.p_double[i] = 0;
        network->columnsigmas.ptr.p_double[i] = 1;
    }
    if( !isclsnet )
    {
        for(i=0; i<=nout-1; i++)
        {
            network->columnmeans.ptr.p_double[nin+i] = 0;
            network->columnsigmas.ptr.p_double[nin+i] = 1;
        }
    }
    ae_frame_leave(_state);
}


/*************************************************************************
Internal subroutine for Hessian calculation.

WARNING!!! Unspeakable math far beyong human capabilities :)
*************************************************************************/
static void mlpbase_mlphessianbatchinternal(multilayerperceptron* network,
     /* Real    */ ae_matrix* xy,
     ae_int_t ssize,
     ae_bool naturalerr,
     double* e,
     /* Real    */ ae_vector* grad,
     /* Real    */ ae_matrix* h,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    ae_int_t ntotal;
    ae_int_t istart;
    ae_int_t i;
    ae_int_t j;
    ae_int_t k;
    ae_int_t kl;
    ae_int_t offs;
    ae_int_t n1;
    ae_int_t n2;
    ae_int_t w1;
    ae_int_t w2;
    double s;
    double t;
    double v;
    double et;
    ae_bool bflag;
    double f;
    double df;
    double d2f;
    double deidyj;
    double mx;
    double q;
    double z;
    double s2;
    double expi;
    double expj;
    ae_vector x;
    ae_vector desiredy;
    ae_vector gt;
    ae_vector zeros;
    ae_matrix rx;
    ae_matrix ry;
    ae_matrix rdx;
    ae_matrix rdy;

    ae_frame_make(_state, &_frame_block);
    *e = 0;
    ae_vector_init(&x, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&desiredy, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&gt, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&zeros, 0, DT_REAL, _state, ae_true);
    ae_matrix_init(&rx, 0, 0, DT_REAL, _state, ae_true);
    ae_matrix_init(&ry, 0, 0, DT_REAL, _state, ae_true);
    ae_matrix_init(&rdx, 0, 0, DT_REAL, _state, ae_true);
    ae_matrix_init(&rdy, 0, 0, DT_REAL, _state, ae_true);

    mlpproperties(network, &nin, &nout, &wcount, _state);
    ntotal = network->structinfo.ptr.p_int[3];
    istart = network->structinfo.ptr.p_int[5];
    
    /*
     * Prepare
     */
    ae_vector_set_length(&x, nin-1+1, _state);
    ae_vector_set_length(&desiredy, nout-1+1, _state);
    ae_vector_set_length(&zeros, wcount-1+1, _state);
    ae_vector_set_length(&gt, wcount-1+1, _state);
    ae_matrix_set_length(&rx, ntotal+nout-1+1, wcount-1+1, _state);
    ae_matrix_set_length(&ry, ntotal+nout-1+1, wcount-1+1, _state);
    ae_matrix_set_length(&rdx, ntotal+nout-1+1, wcount-1+1, _state);
    ae_matrix_set_length(&rdy, ntotal+nout-1+1, wcount-1+1, _state);
    *e = 0;
    for(i=0; i<=wcount-1; i++)
    {
        zeros.ptr.p_double[i] = 0;
    }
    ae_v_move(&grad->ptr.p_double[0], 1, &zeros.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
    for(i=0; i<=wcount-1; i++)
    {
        ae_v_move(&h->ptr.pp_double[i][0], 1, &zeros.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
    }
    
    /*
     * Process
     */
    for(k=0; k<=ssize-1; k++)
    {
        
        /*
         * Process vector with MLPGradN.
         * Now Neurons, DFDNET and DError contains results of the last run.
         */
        ae_v_move(&x.ptr.p_double[0], 1, &xy->ptr.pp_double[k][0], 1, ae_v_len(0,nin-1));
        if( mlpissoftmax(network, _state) )
        {
            
            /*
             * class labels outputs
             */
            kl = ae_round(xy->ptr.pp_double[k][nin], _state);
            for(i=0; i<=nout-1; i++)
            {
                if( i==kl )
                {
                    desiredy.ptr.p_double[i] = 1;
                }
                else
                {
                    desiredy.ptr.p_double[i] = 0;
                }
            }
        }
        else
        {
            
            /*
             * real outputs
             */
            ae_v_move(&desiredy.ptr.p_double[0], 1, &xy->ptr.pp_double[k][nin], 1, ae_v_len(0,nout-1));
        }
        if( naturalerr )
        {
            mlpgradn(network, &x, &desiredy, &et, &gt, _state);
        }
        else
        {
            mlpgrad(network, &x, &desiredy, &et, &gt, _state);
        }
        
        /*
         * grad, error
         */
        *e = *e+et;
        ae_v_add(&grad->ptr.p_double[0], 1, &gt.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
        
        /*
         * Hessian.
         * Forward pass of the R-algorithm
         */
        for(i=0; i<=ntotal-1; i++)
        {
            offs = istart+i*mlpbase_nfieldwidth;
            ae_v_move(&rx.ptr.pp_double[i][0], 1, &zeros.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
            ae_v_move(&ry.ptr.pp_double[i][0], 1, &zeros.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
            if( network->structinfo.ptr.p_int[offs+0]>0||network->structinfo.ptr.p_int[offs+0]==-5 )
            {
                
                /*
                 * Activation function
                 */
                n1 = network->structinfo.ptr.p_int[offs+2];
                ae_v_move(&rx.ptr.pp_double[i][0], 1, &ry.ptr.pp_double[n1][0], 1, ae_v_len(0,wcount-1));
                v = network->dfdnet.ptr.p_double[i];
                ae_v_moved(&ry.ptr.pp_double[i][0], 1, &rx.ptr.pp_double[i][0], 1, ae_v_len(0,wcount-1), v);
                continue;
            }
            if( network->structinfo.ptr.p_int[offs+0]==0 )
            {
                
                /*
                 * Adaptive summator
                 */
                n1 = network->structinfo.ptr.p_int[offs+2];
                n2 = n1+network->structinfo.ptr.p_int[offs+1]-1;
                w1 = network->structinfo.ptr.p_int[offs+3];
                w2 = w1+network->structinfo.ptr.p_int[offs+1]-1;
                for(j=n1; j<=n2; j++)
                {
                    v = network->weights.ptr.p_double[w1+j-n1];
                    ae_v_addd(&rx.ptr.pp_double[i][0], 1, &ry.ptr.pp_double[j][0], 1, ae_v_len(0,wcount-1), v);
                    rx.ptr.pp_double[i][w1+j-n1] = rx.ptr.pp_double[i][w1+j-n1]+network->neurons.ptr.p_double[j];
                }
                ae_v_move(&ry.ptr.pp_double[i][0], 1, &rx.ptr.pp_double[i][0], 1, ae_v_len(0,wcount-1));
                continue;
            }
            if( network->structinfo.ptr.p_int[offs+0]<0 )
            {
                bflag = ae_true;
                if( network->structinfo.ptr.p_int[offs+0]==-2 )
                {
                    
                    /*
                     * input neuron, left unchanged
                     */
                    bflag = ae_false;
                }
                if( network->structinfo.ptr.p_int[offs+0]==-3 )
                {
                    
                    /*
                     * "-1" neuron, left unchanged
                     */
                    bflag = ae_false;
                }
                if( network->structinfo.ptr.p_int[offs+0]==-4 )
                {
                    
                    /*
                     * "0" neuron, left unchanged
                     */
                    bflag = ae_false;
                }
                ae_assert(!bflag, "MLPHessianNBatch: internal error - unknown neuron type!", _state);
                continue;
            }
        }
        
        /*
         * Hessian. Backward pass of the R-algorithm.
         *
         * Stage 1. Initialize RDY
         */
        for(i=0; i<=ntotal+nout-1; i++)
        {
            ae_v_move(&rdy.ptr.pp_double[i][0], 1, &zeros.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
        }
        if( network->structinfo.ptr.p_int[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-1; i++)
            {
                n1 = ntotal-nout+i;
                n2 = ntotal+i;
                
                /*
                 * Forward pass from N1 to N2
                 */
                ae_v_move(&rx.ptr.pp_double[n2][0], 1, &ry.ptr.pp_double[n1][0], 1, ae_v_len(0,wcount-1));
                v = network->columnsigmas.ptr.p_double[nin+i];
                ae_v_moved(&ry.ptr.pp_double[n2][0], 1, &rx.ptr.pp_double[n2][0], 1, ae_v_len(0,wcount-1), v);
                
                /*
                 * Initialization of RDY
                 */
                ae_v_move(&rdy.ptr.pp_double[n2][0], 1, &ry.ptr.pp_double[n2][0], 1, ae_v_len(0,wcount-1));
                
                /*
                 * 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->columnsigmas.ptr.p_double[nin+i];
                ae_v_moved(&rdx.ptr.pp_double[n2][0], 1, &rdy.ptr.pp_double[n2][0], 1, ae_v_len(0,wcount-1), df);
                ae_v_add(&rdy.ptr.pp_double[n1][0], 1, &rdx.ptr.pp_double[n2][0], 1, ae_v_len(0,wcount-1));
            }
        }
        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 = 0;
                for(i=0; i<=nout-1; i++)
                {
                    t = t+desiredy.ptr.p_double[i];
                }
                mx = network->neurons.ptr.p_double[ntotal-nout];
                for(i=0; i<=nout-1; i++)
                {
                    mx = ae_maxreal(mx, network->neurons.ptr.p_double[ntotal-nout+i], _state);
                }
                s = 0;
                for(i=0; i<=nout-1; i++)
                {
                    network->nwbuf.ptr.p_double[i] = ae_exp(network->neurons.ptr.p_double[ntotal-nout+i]-mx, _state);
                    s = s+network->nwbuf.ptr.p_double[i];
                }
                for(i=0; i<=nout-1; i++)
                {
                    for(j=0; j<=nout-1; j++)
                    {
                        if( j==i )
                        {
                            deidyj = t*network->nwbuf.ptr.p_double[i]*(s-network->nwbuf.ptr.p_double[i])/ae_sqr(s, _state);
                            ae_v_addd(&rdy.ptr.pp_double[ntotal-nout+i][0], 1, &ry.ptr.pp_double[ntotal-nout+i][0], 1, ae_v_len(0,wcount-1), deidyj);
                        }
                        else
                        {
                            deidyj = -t*network->nwbuf.ptr.p_double[i]*network->nwbuf.ptr.p_double[j]/ae_sqr(s, _state);
                            ae_v_addd(&rdy.ptr.pp_double[ntotal-nout+i][0], 1, &ry.ptr.pp_double[ntotal-nout+j][0], 1, ae_v_len(0,wcount-1), 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->neurons.ptr.p_double[ntotal-nout];
                for(i=0; i<=nout-1; i++)
                {
                    mx = ae_maxreal(mx, network->neurons.ptr.p_double[ntotal-nout+i], _state);
                }
                s = 0;
                s2 = 0;
                for(i=0; i<=nout-1; i++)
                {
                    network->nwbuf.ptr.p_double[i] = ae_exp(network->neurons.ptr.p_double[ntotal-nout+i]-mx, _state);
                    s = s+network->nwbuf.ptr.p_double[i];
                    s2 = s2+ae_sqr(network->nwbuf.ptr.p_double[i], _state);
                }
                q = 0;
                for(i=0; i<=nout-1; i++)
                {
                    q = q+(network->y.ptr.p_double[i]-desiredy.ptr.p_double[i])*network->nwbuf.ptr.p_double[i];
                }
                for(i=0; i<=nout-1; i++)
                {
                    z = -q+(network->y.ptr.p_double[i]-desiredy.ptr.p_double[i])*s;
                    expi = network->nwbuf.ptr.p_double[i];
                    for(j=0; j<=nout-1; j++)
                    {
                        expj = network->nwbuf.ptr.p_double[j];
                        if( j==i )
                        {
                            deidyj = expi/ae_sqr(s, _state)*((z+expi)*(s-2*expi)/s+expi*s2/ae_sqr(s, _state));
                        }
                        else
                        {
                            deidyj = expi*expj/ae_sqr(s, _state)*(s2/ae_sqr(s, _state)-2*z/s-(expi+expj)/s+(network->y.ptr.p_double[i]-desiredy.ptr.p_double[i])-(network->y.ptr.p_double[j]-desiredy.ptr.p_double[j]));
                        }
                        ae_v_addd(&rdy.ptr.pp_double[ntotal-nout+i][0], 1, &ry.ptr.pp_double[ntotal-nout+j][0], 1, ae_v_len(0,wcount-1), 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*mlpbase_nfieldwidth;
            if( network->structinfo.ptr.p_int[offs+0]>0||network->structinfo.ptr.p_int[offs+0]==-5 )
            {
                n1 = network->structinfo.ptr.p_int[offs+2];
                
                /*
                 * First, calculate R(dE/dX).
                 */
                mlpactivationfunction(network->neurons.ptr.p_double[n1], network->structinfo.ptr.p_int[offs+0], &f, &df, &d2f, _state);
                v = d2f*network->derror.ptr.p_double[i];
                ae_v_moved(&rdx.ptr.pp_double[i][0], 1, &rdy.ptr.pp_double[i][0], 1, ae_v_len(0,wcount-1), df);
                ae_v_addd(&rdx.ptr.pp_double[i][0], 1, &rx.ptr.pp_double[i][0], 1, ae_v_len(0,wcount-1), 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)
                 */
                ae_v_add(&rdy.ptr.pp_double[n1][0], 1, &rdx.ptr.pp_double[i][0], 1, ae_v_len(0,wcount-1));
                continue;
            }
            if( network->structinfo.ptr.p_int[offs+0]==0 )
            {
                
                /*
                 * Adaptive summator
                 */
                n1 = network->structinfo.ptr.p_int[offs+2];
                n2 = n1+network->structinfo.ptr.p_int[offs+1]-1;
                w1 = network->structinfo.ptr.p_int[offs+3];
                w2 = w1+network->structinfo.ptr.p_int[offs+1]-1;
                
                /*
                 * First, calculate R(dE/dX).
                 */
                ae_v_move(&rdx.ptr.pp_double[i][0], 1, &rdy.ptr.pp_double[i][0], 1, ae_v_len(0,wcount-1));
                
                /*
                 * Then, calculate R(dE/dWij)
                 */
                for(j=w1; j<=w2; j++)
                {
                    v = network->neurons.ptr.p_double[n1+j-w1];
                    ae_v_addd(&h->ptr.pp_double[j][0], 1, &rdx.ptr.pp_double[i][0], 1, ae_v_len(0,wcount-1), v);
                    v = network->derror.ptr.p_double[i];
                    ae_v_addd(&h->ptr.pp_double[j][0], 1, &ry.ptr.pp_double[n1+j-w1][0], 1, ae_v_len(0,wcount-1), v);
                }
                
                /*
                 * And finally, update R(dE/dY) for connected neurons.
                 */
                for(j=w1; j<=w2; j++)
                {
                    v = network->weights.ptr.p_double[j];
                    ae_v_addd(&rdy.ptr.pp_double[n1+j-w1][0], 1, &rdx.ptr.pp_double[i][0], 1, ae_v_len(0,wcount-1), v);
                    rdy.ptr.pp_double[n1+j-w1][j] = rdy.ptr.pp_double[n1+j-w1][j]+network->derror.ptr.p_double[i];
                }
                continue;
            }
            if( network->structinfo.ptr.p_int[offs+0]<0 )
            {
                bflag = ae_false;
                if( (network->structinfo.ptr.p_int[offs+0]==-2||network->structinfo.ptr.p_int[offs+0]==-3)||network->structinfo.ptr.p_int[offs+0]==-4 )
                {
                    
                    /*
                     * Special neuron type, no back-propagation required
                     */
                    bflag = ae_true;
                }
                ae_assert(bflag, "MLPHessianNBatch: unknown neuron type!", _state);
                continue;
            }
        }
    }
    ae_frame_leave(_state);
}


/*************************************************************************
Internal subroutine

Network must be processed by MLPProcess on X
*************************************************************************/
static void mlpbase_mlpinternalcalculategradient(multilayerperceptron* network,
     /* Real    */ ae_vector* neurons,
     /* Real    */ ae_vector* weights,
     /* Real    */ ae_vector* derror,
     /* Real    */ ae_vector* grad,
     ae_bool naturalerrorfunc,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t n1;
    ae_int_t n2;
    ae_int_t w1;
    ae_int_t w2;
    ae_int_t ntotal;
    ae_int_t istart;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t offs;
    double dedf;
    double dfdnet;
    double v;
    double fown;
    double deown;
    double net;
    double mx;
    ae_bool bflag;


    
    /*
     * Read network geometry
     */
    nin = network->structinfo.ptr.p_int[1];
    nout = network->structinfo.ptr.p_int[2];
    ntotal = network->structinfo.ptr.p_int[3];
    istart = network->structinfo.ptr.p_int[5];
    
    /*
     * Pre-processing of dError/dOut:
     * from dError/dOut(normalized) to dError/dOut(non-normalized)
     */
    ae_assert(network->structinfo.ptr.p_int[6]==0||network->structinfo.ptr.p_int[6]==1, "MLPInternalCalculateGradient: unknown normalization type!", _state);
    if( network->structinfo.ptr.p_int[6]==1 )
    {
        
        /*
         * Softmax
         */
        if( !naturalerrorfunc )
        {
            mx = network->neurons.ptr.p_double[ntotal-nout];
            for(i=0; i<=nout-1; i++)
            {
                mx = ae_maxreal(mx, network->neurons.ptr.p_double[ntotal-nout+i], _state);
            }
            net = 0;
            for(i=0; i<=nout-1; i++)
            {
                network->nwbuf.ptr.p_double[i] = ae_exp(network->neurons.ptr.p_double[ntotal-nout+i]-mx, _state);
                net = net+network->nwbuf.ptr.p_double[i];
            }
            v = ae_v_dotproduct(&network->derror.ptr.p_double[ntotal-nout], 1, &network->nwbuf.ptr.p_double[0], 1, ae_v_len(ntotal-nout,ntotal-1));
            for(i=0; i<=nout-1; i++)
            {
                fown = network->nwbuf.ptr.p_double[i];
                deown = network->derror.ptr.p_double[ntotal-nout+i];
                network->nwbuf.ptr.p_double[nout+i] = (-v+deown*fown+deown*(net-fown))*fown/ae_sqr(net, _state);
            }
            for(i=0; i<=nout-1; i++)
            {
                network->derror.ptr.p_double[ntotal-nout+i] = network->nwbuf.ptr.p_double[nout+i];
            }
        }
    }
    else
    {
        
        /*
         * Un-standardisation
         */
        for(i=0; i<=nout-1; i++)
        {
            network->derror.ptr.p_double[ntotal-nout+i] = network->derror.ptr.p_double[ntotal-nout+i]*network->columnsigmas.ptr.p_double[nin+i];
        }
    }
    
    /*
     * Backpropagation
     */
    for(i=ntotal-1; i>=0; i--)
    {
        
        /*
         * Extract info
         */
        offs = istart+i*mlpbase_nfieldwidth;
        if( network->structinfo.ptr.p_int[offs+0]>0||network->structinfo.ptr.p_int[offs+0]==-5 )
        {
            
            /*
             * Activation function
             */
            dedf = network->derror.ptr.p_double[i];
            dfdnet = network->dfdnet.ptr.p_double[i];
            derror->ptr.p_double[network->structinfo.ptr.p_int[offs+2]] = derror->ptr.p_double[network->structinfo.ptr.p_int[offs+2]]+dedf*dfdnet;
            continue;
        }
        if( network->structinfo.ptr.p_int[offs+0]==0 )
        {
            
            /*
             * Adaptive summator
             */
            n1 = network->structinfo.ptr.p_int[offs+2];
            n2 = n1+network->structinfo.ptr.p_int[offs+1]-1;
            w1 = network->structinfo.ptr.p_int[offs+3];
            w2 = w1+network->structinfo.ptr.p_int[offs+1]-1;
            dedf = network->derror.ptr.p_double[i];
            dfdnet = 1.0;
            v = dedf*dfdnet;
            ae_v_moved(&grad->ptr.p_double[w1], 1, &neurons->ptr.p_double[n1], 1, ae_v_len(w1,w2), v);
            ae_v_addd(&derror->ptr.p_double[n1], 1, &weights->ptr.p_double[w1], 1, ae_v_len(n1,n2), v);
            continue;
        }
        if( network->structinfo.ptr.p_int[offs+0]<0 )
        {
            bflag = ae_false;
            if( (network->structinfo.ptr.p_int[offs+0]==-2||network->structinfo.ptr.p_int[offs+0]==-3)||network->structinfo.ptr.p_int[offs+0]==-4 )
            {
                
                /*
                 * Special neuron type, no back-propagation required
                 */
                bflag = ae_true;
            }
            ae_assert(bflag, "MLPInternalCalculateGradient: unknown neuron type!", _state);
            continue;
        }
    }
}


/*************************************************************************
Internal subroutine, chunked gradient
*************************************************************************/
static void mlpbase_mlpchunkedgradient(multilayerperceptron* network,
     /* Real    */ ae_matrix* xy,
     ae_int_t cstart,
     ae_int_t csize,
     double* e,
     /* Real    */ ae_vector* grad,
     ae_bool naturalerrorfunc,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t j;
    ae_int_t k;
    ae_int_t kl;
    ae_int_t n1;
    ae_int_t n2;
    ae_int_t w1;
    ae_int_t w2;
    ae_int_t c1;
    ae_int_t c2;
    ae_int_t ntotal;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t offs;
    double f;
    double df;
    double d2f;
    double v;
    double s;
    double fown;
    double deown;
    double net;
    double lnnet;
    double mx;
    ae_bool bflag;
    ae_int_t istart;
    ae_int_t ineurons;
    ae_int_t idfdnet;
    ae_int_t iderror;
    ae_int_t izeros;


    
    /*
     * Read network geometry, prepare data
     */
    nin = network->structinfo.ptr.p_int[1];
    nout = network->structinfo.ptr.p_int[2];
    ntotal = network->structinfo.ptr.p_int[3];
    istart = network->structinfo.ptr.p_int[5];
    c1 = cstart;
    c2 = cstart+csize-1;
    ineurons = 0;
    idfdnet = ntotal;
    iderror = 2*ntotal;
    izeros = 3*ntotal;
    for(j=0; j<=csize-1; j++)
    {
        network->chunks.ptr.pp_double[izeros][j] = 0;
    }
    
    /*
     * Forward pass:
     * 1. Load inputs from XY to Chunks[0:NIn-1,0:CSize-1]
     * 2. Forward pass
     */
    for(i=0; i<=nin-1; i++)
    {
        for(j=0; j<=csize-1; j++)
        {
            if( ae_fp_neq(network->columnsigmas.ptr.p_double[i],0) )
            {
                network->chunks.ptr.pp_double[i][j] = (xy->ptr.pp_double[c1+j][i]-network->columnmeans.ptr.p_double[i])/network->columnsigmas.ptr.p_double[i];
            }
            else
            {
                network->chunks.ptr.pp_double[i][j] = xy->ptr.pp_double[c1+j][i]-network->columnmeans.ptr.p_double[i];
            }
        }
    }
    for(i=0; i<=ntotal-1; i++)
    {
        offs = istart+i*mlpbase_nfieldwidth;
        if( network->structinfo.ptr.p_int[offs+0]>0||network->structinfo.ptr.p_int[offs+0]==-5 )
        {
            
            /*
             * Activation function:
             * * calculate F vector, F(i) = F(NET(i))
             */
            n1 = network->structinfo.ptr.p_int[offs+2];
            ae_v_move(&network->chunks.ptr.pp_double[i][0], 1, &network->chunks.ptr.pp_double[n1][0], 1, ae_v_len(0,csize-1));
            for(j=0; j<=csize-1; j++)
            {
                mlpactivationfunction(network->chunks.ptr.pp_double[i][j], network->structinfo.ptr.p_int[offs+0], &f, &df, &d2f, _state);
                network->chunks.ptr.pp_double[i][j] = f;
                network->chunks.ptr.pp_double[idfdnet+i][j] = df;
            }
            continue;
        }
        if( network->structinfo.ptr.p_int[offs+0]==0 )
        {
            
            /*
             * Adaptive summator:
             * * calculate NET vector, NET(i) = SUM(W(j,i)*Neurons(j),j=N1..N2)
             */
            n1 = network->structinfo.ptr.p_int[offs+2];
            n2 = n1+network->structinfo.ptr.p_int[offs+1]-1;
            w1 = network->structinfo.ptr.p_int[offs+3];
            w2 = w1+network->structinfo.ptr.p_int[offs+1]-1;
            ae_v_move(&network->chunks.ptr.pp_double[i][0], 1, &network->chunks.ptr.pp_double[izeros][0], 1, ae_v_len(0,csize-1));
            for(j=n1; j<=n2; j++)
            {
                v = network->weights.ptr.p_double[w1+j-n1];
                ae_v_addd(&network->chunks.ptr.pp_double[i][0], 1, &network->chunks.ptr.pp_double[j][0], 1, ae_v_len(0,csize-1), v);
            }
            continue;
        }
        if( network->structinfo.ptr.p_int[offs+0]<0 )
        {
            bflag = ae_false;
            if( network->structinfo.ptr.p_int[offs+0]==-2 )
            {
                
                /*
                 * input neuron, left unchanged
                 */
                bflag = ae_true;
            }
            if( network->structinfo.ptr.p_int[offs+0]==-3 )
            {
                
                /*
                 * "-1" neuron
                 */
                for(k=0; k<=csize-1; k++)
                {
                    network->chunks.ptr.pp_double[i][k] = -1;
                }
                bflag = ae_true;
            }
            if( network->structinfo.ptr.p_int[offs+0]==-4 )
            {
                
                /*
                 * "0" neuron
                 */
                for(k=0; k<=csize-1; k++)
                {
                    network->chunks.ptr.pp_double[i][k] = 0;
                }
                bflag = ae_true;
            }
            ae_assert(bflag, "MLPChunkedGradient: internal error - unknown neuron type!", _state);
            continue;
        }
    }
    
    /*
     * Post-processing, error, dError/dOut
     */
    for(i=0; i<=ntotal-1; i++)
    {
        ae_v_move(&network->chunks.ptr.pp_double[iderror+i][0], 1, &network->chunks.ptr.pp_double[izeros][0], 1, ae_v_len(0,csize-1));
    }
    ae_assert(network->structinfo.ptr.p_int[6]==0||network->structinfo.ptr.p_int[6]==1, "MLPChunkedGradient: unknown normalization type!", _state);
    if( network->structinfo.ptr.p_int[6]==1 )
    {
        
        /*
         * Softmax output, classification network.
         *
         * For each K = 0..CSize-1 do:
         * 1. place exp(outputs[k]) to NWBuf[0:NOut-1]
         * 2. place sum(exp(..)) to NET
         * 3. calculate dError/dOut and place it to the second block of Chunks
         */
        for(k=0; k<=csize-1; k++)
        {
            
            /*
             * Normalize
             */
            mx = network->chunks.ptr.pp_double[ntotal-nout][k];
            for(i=1; i<=nout-1; i++)
            {
                mx = ae_maxreal(mx, network->chunks.ptr.pp_double[ntotal-nout+i][k], _state);
            }
            net = 0;
            for(i=0; i<=nout-1; i++)
            {
                network->nwbuf.ptr.p_double[i] = ae_exp(network->chunks.ptr.pp_double[ntotal-nout+i][k]-mx, _state);
                net = net+network->nwbuf.ptr.p_double[i];
            }
            
            /*
             * Calculate error function and dError/dOut
             */
            if( naturalerrorfunc )
            {
                
                /*
                 * Natural error func.
                 *
                 */
                s = 1;
                lnnet = ae_log(net, _state);
                kl = ae_round(xy->ptr.pp_double[cstart+k][nin], _state);
                for(i=0; i<=nout-1; i++)
                {
                    if( i==kl )
                    {
                        v = 1;
                    }
                    else
                    {
                        v = 0;
                    }
                    network->chunks.ptr.pp_double[iderror+ntotal-nout+i][k] = s*network->nwbuf.ptr.p_double[i]/net-v;
                    *e = *e+mlpbase_safecrossentropy(v, network->nwbuf.ptr.p_double[i]/net, _state);
                }
            }
            else
            {
                
                /*
                 * Least squares error func
                 * Error, dError/dOut(normalized)
                 */
                kl = ae_round(xy->ptr.pp_double[cstart+k][nin], _state);
                for(i=0; i<=nout-1; i++)
                {
                    if( i==kl )
                    {
                        v = network->nwbuf.ptr.p_double[i]/net-1;
                    }
                    else
                    {
                        v = network->nwbuf.ptr.p_double[i]/net;
                    }
                    network->nwbuf.ptr.p_double[nout+i] = v;
                    *e = *e+ae_sqr(v, _state)/2;
                }
                
                /*
                 * From dError/dOut(normalized) to dError/dOut(non-normalized)
                 */
                v = ae_v_dotproduct(&network->nwbuf.ptr.p_double[nout], 1, &network->nwbuf.ptr.p_double[0], 1, ae_v_len(nout,2*nout-1));
                for(i=0; i<=nout-1; i++)
                {
                    fown = network->nwbuf.ptr.p_double[i];
                    deown = network->nwbuf.ptr.p_double[nout+i];
                    network->chunks.ptr.pp_double[iderror+ntotal-nout+i][k] = (-v+deown*fown+deown*(net-fown))*fown/ae_sqr(net, _state);
                }
            }
        }
    }
    else
    {
        
        /*
         * Normal output, regression network
         *
         * For each K = 0..CSize-1 do:
         * 1. calculate dError/dOut and place it to the second block of Chunks
         */
        for(i=0; i<=nout-1; i++)
        {
            for(j=0; j<=csize-1; j++)
            {
                v = network->chunks.ptr.pp_double[ntotal-nout+i][j]*network->columnsigmas.ptr.p_double[nin+i]+network->columnmeans.ptr.p_double[nin+i]-xy->ptr.pp_double[cstart+j][nin+i];
                network->chunks.ptr.pp_double[iderror+ntotal-nout+i][j] = v*network->columnsigmas.ptr.p_double[nin+i];
                *e = *e+ae_sqr(v, _state)/2;
            }
        }
    }
    
    /*
     * Backpropagation
     */
    for(i=ntotal-1; i>=0; i--)
    {
        
        /*
         * Extract info
         */
        offs = istart+i*mlpbase_nfieldwidth;
        if( network->structinfo.ptr.p_int[offs+0]>0||network->structinfo.ptr.p_int[offs+0]==-5 )
        {
            
            /*
             * Activation function
             */
            n1 = network->structinfo.ptr.p_int[offs+2];
            for(k=0; k<=csize-1; k++)
            {
                network->chunks.ptr.pp_double[iderror+i][k] = network->chunks.ptr.pp_double[iderror+i][k]*network->chunks.ptr.pp_double[idfdnet+i][k];
            }
            ae_v_add(&network->chunks.ptr.pp_double[iderror+n1][0], 1, &network->chunks.ptr.pp_double[iderror+i][0], 1, ae_v_len(0,csize-1));
            continue;
        }
        if( network->structinfo.ptr.p_int[offs+0]==0 )
        {
            
            /*
             * "Normal" activation function
             */
            n1 = network->structinfo.ptr.p_int[offs+2];
            n2 = n1+network->structinfo.ptr.p_int[offs+1]-1;
            w1 = network->structinfo.ptr.p_int[offs+3];
            w2 = w1+network->structinfo.ptr.p_int[offs+1]-1;
            for(j=w1; j<=w2; j++)
            {
                v = ae_v_dotproduct(&network->chunks.ptr.pp_double[n1+j-w1][0], 1, &network->chunks.ptr.pp_double[iderror+i][0], 1, ae_v_len(0,csize-1));
                grad->ptr.p_double[j] = grad->ptr.p_double[j]+v;
            }
            for(j=n1; j<=n2; j++)
            {
                v = network->weights.ptr.p_double[w1+j-n1];
                ae_v_addd(&network->chunks.ptr.pp_double[iderror+j][0], 1, &network->chunks.ptr.pp_double[iderror+i][0], 1, ae_v_len(0,csize-1), v);
            }
            continue;
        }
        if( network->structinfo.ptr.p_int[offs+0]<0 )
        {
            bflag = ae_false;
            if( (network->structinfo.ptr.p_int[offs+0]==-2||network->structinfo.ptr.p_int[offs+0]==-3)||network->structinfo.ptr.p_int[offs+0]==-4 )
            {
                
                /*
                 * Special neuron type, no back-propagation required
                 */
                bflag = ae_true;
            }
            ae_assert(bflag, "MLPInternalCalculateGradient: unknown neuron type!", _state);
            continue;
        }
    }
}


/*************************************************************************
Returns T*Ln(T/Z), guarded against overflow/underflow.
Internal subroutine.
*************************************************************************/
static double mlpbase_safecrossentropy(double t,
     double z,
     ae_state *_state)
{
    double r;
    double result;


    if( ae_fp_eq(t,0) )
    {
        result = 0;
    }
    else
    {
        if( ae_fp_greater(ae_fabs(z, _state),1) )
        {
            
            /*
             * Shouldn't be the case with softmax,
             * but we just want to be sure.
             */
            if( ae_fp_eq(t/z,0) )
            {
                r = ae_minrealnumber;
            }
            else
            {
                r = t/z;
            }
        }
        else
        {
            
            /*
             * Normal case
             */
            if( ae_fp_eq(z,0)||ae_fp_greater_eq(ae_fabs(t, _state),ae_maxrealnumber*ae_fabs(z, _state)) )
            {
                r = ae_maxrealnumber;
            }
            else
            {
                r = t/z;
            }
        }
        result = t*ae_log(r, _state);
    }
    return result;
}


ae_bool _multilayerperceptron_init(void* _p, ae_state *_state, ae_bool make_automatic)
{
    multilayerperceptron *p = (multilayerperceptron*)_p;
    ae_touch_ptr((void*)p);
    if( !ae_vector_init(&p->hllayersizes, 0, DT_INT, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->hlconnections, 0, DT_INT, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->hlneurons, 0, DT_INT, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->structinfo, 0, DT_INT, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->weights, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->columnmeans, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->columnsigmas, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->neurons, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->dfdnet, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->derror, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->x, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->y, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_matrix_init(&p->xy, 0, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->xyrow, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_matrix_init(&p->chunks, 0, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->nwbuf, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->integerbuf, 0, DT_INT, _state, make_automatic) )
        return ae_false;
    return ae_true;
}


ae_bool _multilayerperceptron_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic)
{
    multilayerperceptron *dst = (multilayerperceptron*)_dst;
    multilayerperceptron *src = (multilayerperceptron*)_src;
    dst->hlnetworktype = src->hlnetworktype;
    dst->hlnormtype = src->hlnormtype;
    if( !ae_vector_init_copy(&dst->hllayersizes, &src->hllayersizes, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->hlconnections, &src->hlconnections, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->hlneurons, &src->hlneurons, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->structinfo, &src->structinfo, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->weights, &src->weights, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->columnmeans, &src->columnmeans, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->columnsigmas, &src->columnsigmas, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->neurons, &src->neurons, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->dfdnet, &src->dfdnet, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->derror, &src->derror, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->x, &src->x, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->y, &src->y, _state, make_automatic) )
        return ae_false;
    if( !ae_matrix_init_copy(&dst->xy, &src->xy, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->xyrow, &src->xyrow, _state, make_automatic) )
        return ae_false;
    if( !ae_matrix_init_copy(&dst->chunks, &src->chunks, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->nwbuf, &src->nwbuf, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->integerbuf, &src->integerbuf, _state, make_automatic) )
        return ae_false;
    return ae_true;
}


void _multilayerperceptron_clear(void* _p)
{
    multilayerperceptron *p = (multilayerperceptron*)_p;
    ae_touch_ptr((void*)p);
    ae_vector_clear(&p->hllayersizes);
    ae_vector_clear(&p->hlconnections);
    ae_vector_clear(&p->hlneurons);
    ae_vector_clear(&p->structinfo);
    ae_vector_clear(&p->weights);
    ae_vector_clear(&p->columnmeans);
    ae_vector_clear(&p->columnsigmas);
    ae_vector_clear(&p->neurons);
    ae_vector_clear(&p->dfdnet);
    ae_vector_clear(&p->derror);
    ae_vector_clear(&p->x);
    ae_vector_clear(&p->y);
    ae_matrix_clear(&p->xy);
    ae_vector_clear(&p->xyrow);
    ae_matrix_clear(&p->chunks);
    ae_vector_clear(&p->nwbuf);
    ae_vector_clear(&p->integerbuf);
}


void _multilayerperceptron_destroy(void* _p)
{
    multilayerperceptron *p = (multilayerperceptron*)_p;
    ae_touch_ptr((void*)p);
    ae_vector_destroy(&p->hllayersizes);
    ae_vector_destroy(&p->hlconnections);
    ae_vector_destroy(&p->hlneurons);
    ae_vector_destroy(&p->structinfo);
    ae_vector_destroy(&p->weights);
    ae_vector_destroy(&p->columnmeans);
    ae_vector_destroy(&p->columnsigmas);
    ae_vector_destroy(&p->neurons);
    ae_vector_destroy(&p->dfdnet);
    ae_vector_destroy(&p->derror);
    ae_vector_destroy(&p->x);
    ae_vector_destroy(&p->y);
    ae_matrix_destroy(&p->xy);
    ae_vector_destroy(&p->xyrow);
    ae_matrix_destroy(&p->chunks);
    ae_vector_destroy(&p->nwbuf);
    ae_vector_destroy(&p->integerbuf);
}


ae_bool _modelerrors_init(void* _p, ae_state *_state, ae_bool make_automatic)
{
    modelerrors *p = (modelerrors*)_p;
    ae_touch_ptr((void*)p);
    return ae_true;
}


ae_bool _modelerrors_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic)
{
    modelerrors *dst = (modelerrors*)_dst;
    modelerrors *src = (modelerrors*)_src;
    dst->relclserror = src->relclserror;
    dst->avgce = src->avgce;
    dst->rmserror = src->rmserror;
    dst->avgerror = src->avgerror;
    dst->avgrelerror = src->avgrelerror;
    return ae_true;
}


void _modelerrors_clear(void* _p)
{
    modelerrors *p = (modelerrors*)_p;
    ae_touch_ptr((void*)p);
}


void _modelerrors_destroy(void* _p)
{
    modelerrors *p = (modelerrors*)_p;
    ae_touch_ptr((void*)p);
}




/*************************************************************************
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

  -- ALGLIB --
     Copyright 10.09.2008 by Bochkanov Sergey
*************************************************************************/
void mnltrainh(/* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_int_t nvars,
     ae_int_t nclasses,
     ae_int_t* info,
     logitmodel* lm,
     mnlreport* rep,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t i;
    ae_int_t j;
    ae_int_t k;
    ae_int_t ssize;
    ae_bool allsame;
    ae_int_t offs;
    double threshold;
    double wminstep;
    double decay;
    ae_int_t wdim;
    ae_int_t expoffs;
    double v;
    double s;
    multilayerperceptron network;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    double e;
    ae_vector g;
    ae_matrix h;
    ae_bool spd;
    ae_vector x;
    ae_vector y;
    ae_vector wbase;
    double wstep;
    ae_vector wdir;
    ae_vector work;
    ae_int_t mcstage;
    logitmcstate mcstate;
    ae_int_t mcinfo;
    ae_int_t mcnfev;
    ae_int_t solverinfo;
    densesolverreport solverrep;

    ae_frame_make(_state, &_frame_block);
    *info = 0;
    _logitmodel_clear(lm);
    _mnlreport_clear(rep);
    _multilayerperceptron_init(&network, _state, ae_true);
    ae_vector_init(&g, 0, DT_REAL, _state, ae_true);
    ae_matrix_init(&h, 0, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&x, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&y, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&wbase, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&wdir, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&work, 0, DT_REAL, _state, ae_true);
    _logitmcstate_init(&mcstate, _state, ae_true);
    _densesolverreport_init(&solverrep, _state, ae_true);

    threshold = 1000*ae_machineepsilon;
    wminstep = 0.001;
    decay = 0.001;
    
    /*
     * Test for inputs
     */
    if( (npoints<nvars+2||nvars<1)||nclasses<2 )
    {
        *info = -1;
        ae_frame_leave(_state);
        return;
    }
    for(i=0; i<=npoints-1; i++)
    {
        if( ae_round(xy->ptr.pp_double[i][nvars], _state)<0||ae_round(xy->ptr.pp_double[i][nvars], _state)>=nclasses )
        {
            *info = -2;
            ae_frame_leave(_state);
            return;
        }
    }
    *info = 1;
    
    /*
     * Initialize data
     */
    rep->ngrad = 0;
    rep->nhess = 0;
    
    /*
     * Allocate array
     */
    wdim = (nvars+1)*(nclasses-1);
    offs = 5;
    expoffs = offs+wdim;
    ssize = 5+(nvars+1)*(nclasses-1)+nclasses;
    ae_vector_set_length(&lm->w, ssize-1+1, _state);
    lm->w.ptr.p_double[0] = ssize;
    lm->w.ptr.p_double[1] = logit_logitvnum;
    lm->w.ptr.p_double[2] = nvars;
    lm->w.ptr.p_double[3] = nclasses;
    lm->w.ptr.p_double[4] = offs;
    
    /*
     * Degenerate case: all outputs are equal
     */
    allsame = ae_true;
    for(i=1; i<=npoints-1; i++)
    {
        if( ae_round(xy->ptr.pp_double[i][nvars], _state)!=ae_round(xy->ptr.pp_double[i-1][nvars], _state) )
        {
            allsame = ae_false;
        }
    }
    if( allsame )
    {
        for(i=0; i<=(nvars+1)*(nclasses-1)-1; i++)
        {
            lm->w.ptr.p_double[offs+i] = 0;
        }
        v = -2*ae_log(ae_minrealnumber, _state);
        k = ae_round(xy->ptr.pp_double[0][nvars], _state);
        if( k==nclasses-1 )
        {
            for(i=0; i<=nclasses-2; i++)
            {
                lm->w.ptr.p_double[offs+i*(nvars+1)+nvars] = -v;
            }
        }
        else
        {
            for(i=0; i<=nclasses-2; i++)
            {
                if( i==k )
                {
                    lm->w.ptr.p_double[offs+i*(nvars+1)+nvars] = v;
                }
                else
                {
                    lm->w.ptr.p_double[offs+i*(nvars+1)+nvars] = 0;
                }
            }
        }
        ae_frame_leave(_state);
        return;
    }
    
    /*
     * General case.
     * Prepare task and network. Allocate space.
     */
    mlpcreatec0(nvars, nclasses, &network, _state);
    mlpinitpreprocessor(&network, xy, npoints, _state);
    mlpproperties(&network, &nin, &nout, &wcount, _state);
    for(i=0; i<=wcount-1; i++)
    {
        network.weights.ptr.p_double[i] = (2*ae_randomreal(_state)-1)/nvars;
    }
    ae_vector_set_length(&g, wcount-1+1, _state);
    ae_matrix_set_length(&h, wcount-1+1, wcount-1+1, _state);
    ae_vector_set_length(&wbase, wcount-1+1, _state);
    ae_vector_set_length(&wdir, wcount-1+1, _state);
    ae_vector_set_length(&work, wcount-1+1, _state);
    
    /*
     * First stage: optimize in gradient direction.
     */
    for(k=0; k<=wcount/3+10; k++)
    {
        
        /*
         * Calculate gradient in starting point
         */
        mlpgradnbatch(&network, xy, npoints, &e, &g, _state);
        v = ae_v_dotproduct(&network.weights.ptr.p_double[0], 1, &network.weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
        e = e+0.5*decay*v;
        ae_v_addd(&g.ptr.p_double[0], 1, &network.weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1), decay);
        rep->ngrad = rep->ngrad+1;
        
        /*
         * Setup optimization scheme
         */
        ae_v_moveneg(&wdir.ptr.p_double[0], 1, &g.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
        v = ae_v_dotproduct(&wdir.ptr.p_double[0], 1, &wdir.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
        wstep = ae_sqrt(v, _state);
        v = 1/ae_sqrt(v, _state);
        ae_v_muld(&wdir.ptr.p_double[0], 1, ae_v_len(0,wcount-1), v);
        mcstage = 0;
        logit_mnlmcsrch(wcount, &network.weights, &e, &g, &wdir, &wstep, &mcinfo, &mcnfev, &work, &mcstate, &mcstage, _state);
        while(mcstage!=0)
        {
            mlpgradnbatch(&network, xy, npoints, &e, &g, _state);
            v = ae_v_dotproduct(&network.weights.ptr.p_double[0], 1, &network.weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
            e = e+0.5*decay*v;
            ae_v_addd(&g.ptr.p_double[0], 1, &network.weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1), decay);
            rep->ngrad = rep->ngrad+1;
            logit_mnlmcsrch(wcount, &network.weights, &e, &g, &wdir, &wstep, &mcinfo, &mcnfev, &work, &mcstate, &mcstage, _state);
        }
    }
    
    /*
     * Second stage: use Hessian when we are close to the minimum
     */
    for(;;)
    {
        
        /*
         * Calculate and update E/G/H
         */
        mlphessiannbatch(&network, xy, npoints, &e, &g, &h, _state);
        v = ae_v_dotproduct(&network.weights.ptr.p_double[0], 1, &network.weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
        e = e+0.5*decay*v;
        ae_v_addd(&g.ptr.p_double[0], 1, &network.weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1), decay);
        for(k=0; k<=wcount-1; k++)
        {
            h.ptr.pp_double[k][k] = h.ptr.pp_double[k][k]+decay;
        }
        rep->nhess = rep->nhess+1;
        
        /*
         * Select step direction
         * NOTE: it is important to use lower-triangle Cholesky
         * factorization since it is much faster than higher-triangle version.
         */
        spd = spdmatrixcholesky(&h, wcount, ae_false, _state);
        spdmatrixcholeskysolve(&h, wcount, ae_false, &g, &solverinfo, &solverrep, &wdir, _state);
        spd = solverinfo>0;
        if( spd )
        {
            
            /*
             * H is positive definite.
             * Step in Newton direction.
             */
            ae_v_muld(&wdir.ptr.p_double[0], 1, ae_v_len(0,wcount-1), -1);
            spd = ae_true;
        }
        else
        {
            
            /*
             * H is indefinite.
             * Step in gradient direction.
             */
            ae_v_moveneg(&wdir.ptr.p_double[0], 1, &g.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
            spd = ae_false;
        }
        
        /*
         * Optimize in WDir direction
         */
        v = ae_v_dotproduct(&wdir.ptr.p_double[0], 1, &wdir.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
        wstep = ae_sqrt(v, _state);
        v = 1/ae_sqrt(v, _state);
        ae_v_muld(&wdir.ptr.p_double[0], 1, ae_v_len(0,wcount-1), v);
        mcstage = 0;
        logit_mnlmcsrch(wcount, &network.weights, &e, &g, &wdir, &wstep, &mcinfo, &mcnfev, &work, &mcstate, &mcstage, _state);
        while(mcstage!=0)
        {
            mlpgradnbatch(&network, xy, npoints, &e, &g, _state);
            v = ae_v_dotproduct(&network.weights.ptr.p_double[0], 1, &network.weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
            e = e+0.5*decay*v;
            ae_v_addd(&g.ptr.p_double[0], 1, &network.weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1), decay);
            rep->ngrad = rep->ngrad+1;
            logit_mnlmcsrch(wcount, &network.weights, &e, &g, &wdir, &wstep, &mcinfo, &mcnfev, &work, &mcstate, &mcstage, _state);
        }
        if( spd&&((mcinfo==2||mcinfo==4)||mcinfo==6) )
        {
            break;
        }
    }
    
    /*
     * Convert from NN format to MNL format
     */
    ae_v_move(&lm->w.ptr.p_double[offs], 1, &network.weights.ptr.p_double[0], 1, ae_v_len(offs,offs+wcount-1));
    for(k=0; k<=nvars-1; k++)
    {
        for(i=0; i<=nclasses-2; i++)
        {
            s = network.columnsigmas.ptr.p_double[k];
            if( ae_fp_eq(s,0) )
            {
                s = 1;
            }
            j = offs+(nvars+1)*i;
            v = lm->w.ptr.p_double[j+k];
            lm->w.ptr.p_double[j+k] = v/s;
            lm->w.ptr.p_double[j+nvars] = lm->w.ptr.p_double[j+nvars]+v*network.columnmeans.ptr.p_double[k]/s;
        }
    }
    for(k=0; k<=nclasses-2; k++)
    {
        lm->w.ptr.p_double[offs+(nvars+1)*k+nvars] = -lm->w.ptr.p_double[offs+(nvars+1)*k+nvars];
    }
    ae_frame_leave(_state);
}


/*************************************************************************
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.

  -- ALGLIB --
     Copyright 10.09.2008 by Bochkanov Sergey
*************************************************************************/
void mnlprocess(logitmodel* lm,
     /* Real    */ ae_vector* x,
     /* Real    */ ae_vector* y,
     ae_state *_state)
{
    ae_int_t nvars;
    ae_int_t nclasses;
    ae_int_t offs;
    ae_int_t i;
    ae_int_t i1;
    double s;


    ae_assert(ae_fp_eq(lm->w.ptr.p_double[1],logit_logitvnum), "MNLProcess: unexpected model version", _state);
    nvars = ae_round(lm->w.ptr.p_double[2], _state);
    nclasses = ae_round(lm->w.ptr.p_double[3], _state);
    offs = ae_round(lm->w.ptr.p_double[4], _state);
    logit_mnliexp(&lm->w, x, _state);
    s = 0;
    i1 = offs+(nvars+1)*(nclasses-1);
    for(i=i1; i<=i1+nclasses-1; i++)
    {
        s = s+lm->w.ptr.p_double[i];
    }
    if( y->cnt<nclasses )
    {
        ae_vector_set_length(y, nclasses, _state);
    }
    for(i=0; i<=nclasses-1; i++)
    {
        y->ptr.p_double[i] = lm->w.ptr.p_double[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.

  -- ALGLIB --
     Copyright 10.09.2008 by Bochkanov Sergey
*************************************************************************/
void mnlprocessi(logitmodel* lm,
     /* Real    */ ae_vector* x,
     /* Real    */ ae_vector* y,
     ae_state *_state)
{

    ae_vector_clear(y);

    mnlprocess(lm, x, y, _state);
}


/*************************************************************************
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

  -- ALGLIB --
     Copyright 10.09.2008 by Bochkanov Sergey
*************************************************************************/
void mnlunpack(logitmodel* lm,
     /* Real    */ ae_matrix* a,
     ae_int_t* nvars,
     ae_int_t* nclasses,
     ae_state *_state)
{
    ae_int_t offs;
    ae_int_t i;

    ae_matrix_clear(a);
    *nvars = 0;
    *nclasses = 0;

    ae_assert(ae_fp_eq(lm->w.ptr.p_double[1],logit_logitvnum), "MNLUnpack: unexpected model version", _state);
    *nvars = ae_round(lm->w.ptr.p_double[2], _state);
    *nclasses = ae_round(lm->w.ptr.p_double[3], _state);
    offs = ae_round(lm->w.ptr.p_double[4], _state);
    ae_matrix_set_length(a, *nclasses-2+1, *nvars+1, _state);
    for(i=0; i<=*nclasses-2; i++)
    {
        ae_v_move(&a->ptr.pp_double[i][0], 1, &lm->w.ptr.p_double[offs+i*(*nvars+1)], 1, ae_v_len(0,*nvars));
    }
}


/*************************************************************************
"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.

  -- ALGLIB --
     Copyright 10.09.2008 by Bochkanov Sergey
*************************************************************************/
void mnlpack(/* Real    */ ae_matrix* a,
     ae_int_t nvars,
     ae_int_t nclasses,
     logitmodel* lm,
     ae_state *_state)
{
    ae_int_t offs;
    ae_int_t i;
    ae_int_t wdim;
    ae_int_t ssize;

    _logitmodel_clear(lm);

    wdim = (nvars+1)*(nclasses-1);
    offs = 5;
    ssize = 5+(nvars+1)*(nclasses-1)+nclasses;
    ae_vector_set_length(&lm->w, ssize-1+1, _state);
    lm->w.ptr.p_double[0] = ssize;
    lm->w.ptr.p_double[1] = logit_logitvnum;
    lm->w.ptr.p_double[2] = nvars;
    lm->w.ptr.p_double[3] = nclasses;
    lm->w.ptr.p_double[4] = offs;
    for(i=0; i<=nclasses-2; i++)
    {
        ae_v_move(&lm->w.ptr.p_double[offs+i*(nvars+1)], 1, &a->ptr.pp_double[i][0], 1, ae_v_len(offs+i*(nvars+1),offs+i*(nvars+1)+nvars));
    }
}


/*************************************************************************
Copying of LogitModel strucure

INPUT PARAMETERS:
    LM1 -   original

OUTPUT PARAMETERS:
    LM2 -   copy

  -- ALGLIB --
     Copyright 15.03.2009 by Bochkanov Sergey
*************************************************************************/
void mnlcopy(logitmodel* lm1, logitmodel* lm2, ae_state *_state)
{
    ae_int_t k;

    _logitmodel_clear(lm2);

    k = ae_round(lm1->w.ptr.p_double[0], _state);
    ae_vector_set_length(&lm2->w, k-1+1, _state);
    ae_v_move(&lm2->w.ptr.p_double[0], 1, &lm1->w.ptr.p_double[0], 1, ae_v_len(0,k-1));
}


/*************************************************************************
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)).

  -- ALGLIB --
     Copyright 10.09.2008 by Bochkanov Sergey
*************************************************************************/
double mnlavgce(logitmodel* lm,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t nvars;
    ae_int_t nclasses;
    ae_int_t i;
    ae_vector workx;
    ae_vector worky;
    double result;

    ae_frame_make(_state, &_frame_block);
    ae_vector_init(&workx, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&worky, 0, DT_REAL, _state, ae_true);

    ae_assert(ae_fp_eq(lm->w.ptr.p_double[1],logit_logitvnum), "MNLClsError: unexpected model version", _state);
    nvars = ae_round(lm->w.ptr.p_double[2], _state);
    nclasses = ae_round(lm->w.ptr.p_double[3], _state);
    ae_vector_set_length(&workx, nvars-1+1, _state);
    ae_vector_set_length(&worky, nclasses-1+1, _state);
    result = 0;
    for(i=0; i<=npoints-1; i++)
    {
        ae_assert(ae_round(xy->ptr.pp_double[i][nvars], _state)>=0&&ae_round(xy->ptr.pp_double[i][nvars], _state)<nclasses, "MNLAvgCE: incorrect class number!", _state);
        
        /*
         * Process
         */
        ae_v_move(&workx.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1));
        mnlprocess(lm, &workx, &worky, _state);
        if( ae_fp_greater(worky.ptr.p_double[ae_round(xy->ptr.pp_double[i][nvars], _state)],0) )
        {
            result = result-ae_log(worky.ptr.p_double[ae_round(xy->ptr.pp_double[i][nvars], _state)], _state);
        }
        else
        {
            result = result-ae_log(ae_minrealnumber, _state);
        }
    }
    result = result/(npoints*ae_log(2, _state));
    ae_frame_leave(_state);
    return result;
}


/*************************************************************************
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.

  -- ALGLIB --
     Copyright 10.09.2008 by Bochkanov Sergey
*************************************************************************/
double mnlrelclserror(logitmodel* lm,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_state *_state)
{
    double result;


    result = (double)mnlclserror(lm, xy, npoints, _state)/(double)npoints;
    return result;
}


/*************************************************************************
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).

  -- ALGLIB --
     Copyright 30.08.2008 by Bochkanov Sergey
*************************************************************************/
double mnlrmserror(logitmodel* lm,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_state *_state)
{
    double relcls;
    double avgce;
    double rms;
    double avg;
    double avgrel;
    double result;


    ae_assert(ae_round(lm->w.ptr.p_double[1], _state)==logit_logitvnum, "MNLRMSError: Incorrect MNL version!", _state);
    logit_mnlallerrors(lm, xy, npoints, &relcls, &avgce, &rms, &avg, &avgrel, _state);
    result = rms;
    return result;
}


/*************************************************************************
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).

  -- ALGLIB --
     Copyright 30.08.2008 by Bochkanov Sergey
*************************************************************************/
double mnlavgerror(logitmodel* lm,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_state *_state)
{
    double relcls;
    double avgce;
    double rms;
    double avg;
    double avgrel;
    double result;


    ae_assert(ae_round(lm->w.ptr.p_double[1], _state)==logit_logitvnum, "MNLRMSError: Incorrect MNL version!", _state);
    logit_mnlallerrors(lm, xy, npoints, &relcls, &avgce, &rms, &avg, &avgrel, _state);
    result = avg;
    return result;
}


/*************************************************************************
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).

  -- ALGLIB --
     Copyright 30.08.2008 by Bochkanov Sergey
*************************************************************************/
double mnlavgrelerror(logitmodel* lm,
     /* Real    */ ae_matrix* xy,
     ae_int_t ssize,
     ae_state *_state)
{
    double relcls;
    double avgce;
    double rms;
    double avg;
    double avgrel;
    double result;


    ae_assert(ae_round(lm->w.ptr.p_double[1], _state)==logit_logitvnum, "MNLRMSError: Incorrect MNL version!", _state);
    logit_mnlallerrors(lm, xy, ssize, &relcls, &avgce, &rms, &avg, &avgrel, _state);
    result = avgrel;
    return result;
}


/*************************************************************************
Classification error on test set = MNLRelClsError*NPoints

  -- ALGLIB --
     Copyright 10.09.2008 by Bochkanov Sergey
*************************************************************************/
ae_int_t mnlclserror(logitmodel* lm,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t nvars;
    ae_int_t nclasses;
    ae_int_t i;
    ae_int_t j;
    ae_vector workx;
    ae_vector worky;
    ae_int_t nmax;
    ae_int_t result;

    ae_frame_make(_state, &_frame_block);
    ae_vector_init(&workx, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&worky, 0, DT_REAL, _state, ae_true);

    ae_assert(ae_fp_eq(lm->w.ptr.p_double[1],logit_logitvnum), "MNLClsError: unexpected model version", _state);
    nvars = ae_round(lm->w.ptr.p_double[2], _state);
    nclasses = ae_round(lm->w.ptr.p_double[3], _state);
    ae_vector_set_length(&workx, nvars-1+1, _state);
    ae_vector_set_length(&worky, nclasses-1+1, _state);
    result = 0;
    for(i=0; i<=npoints-1; i++)
    {
        
        /*
         * Process
         */
        ae_v_move(&workx.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1));
        mnlprocess(lm, &workx, &worky, _state);
        
        /*
         * Logit version of the answer
         */
        nmax = 0;
        for(j=0; j<=nclasses-1; j++)
        {
            if( ae_fp_greater(worky.ptr.p_double[j],worky.ptr.p_double[nmax]) )
            {
                nmax = j;
            }
        }
        
        /*
         * compare
         */
        if( nmax!=ae_round(xy->ptr.pp_double[i][nvars], _state) )
        {
            result = result+1;
        }
    }
    ae_frame_leave(_state);
    return result;
}


/*************************************************************************
Internal subroutine. Places exponents of the anti-overflow shifted
internal linear outputs into the service part of the W array.
*************************************************************************/
static void logit_mnliexp(/* Real    */ ae_vector* w,
     /* Real    */ ae_vector* x,
     ae_state *_state)
{
    ae_int_t nvars;
    ae_int_t nclasses;
    ae_int_t offs;
    ae_int_t i;
    ae_int_t i1;
    double v;
    double mx;


    ae_assert(ae_fp_eq(w->ptr.p_double[1],logit_logitvnum), "LOGIT: unexpected model version", _state);
    nvars = ae_round(w->ptr.p_double[2], _state);
    nclasses = ae_round(w->ptr.p_double[3], _state);
    offs = ae_round(w->ptr.p_double[4], _state);
    i1 = offs+(nvars+1)*(nclasses-1);
    for(i=0; i<=nclasses-2; i++)
    {
        v = ae_v_dotproduct(&w->ptr.p_double[offs+i*(nvars+1)], 1, &x->ptr.p_double[0], 1, ae_v_len(offs+i*(nvars+1),offs+i*(nvars+1)+nvars-1));
        w->ptr.p_double[i1+i] = v+w->ptr.p_double[offs+i*(nvars+1)+nvars];
    }
    w->ptr.p_double[i1+nclasses-1] = 0;
    mx = 0;
    for(i=i1; i<=i1+nclasses-1; i++)
    {
        mx = ae_maxreal(mx, w->ptr.p_double[i], _state);
    }
    for(i=i1; i<=i1+nclasses-1; i++)
    {
        w->ptr.p_double[i] = ae_exp(w->ptr.p_double[i]-mx, _state);
    }
}


/*************************************************************************
Calculation of all types of errors

  -- ALGLIB --
     Copyright 30.08.2008 by Bochkanov Sergey
*************************************************************************/
static void logit_mnlallerrors(logitmodel* lm,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     double* relcls,
     double* avgce,
     double* rms,
     double* avg,
     double* avgrel,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t nvars;
    ae_int_t nclasses;
    ae_int_t i;
    ae_vector buf;
    ae_vector workx;
    ae_vector y;
    ae_vector dy;

    ae_frame_make(_state, &_frame_block);
    *relcls = 0;
    *avgce = 0;
    *rms = 0;
    *avg = 0;
    *avgrel = 0;
    ae_vector_init(&buf, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&workx, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&y, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&dy, 0, DT_REAL, _state, ae_true);

    ae_assert(ae_round(lm->w.ptr.p_double[1], _state)==logit_logitvnum, "MNL unit: Incorrect MNL version!", _state);
    nvars = ae_round(lm->w.ptr.p_double[2], _state);
    nclasses = ae_round(lm->w.ptr.p_double[3], _state);
    ae_vector_set_length(&workx, nvars-1+1, _state);
    ae_vector_set_length(&y, nclasses-1+1, _state);
    ae_vector_set_length(&dy, 0+1, _state);
    dserrallocate(nclasses, &buf, _state);
    for(i=0; i<=npoints-1; i++)
    {
        ae_v_move(&workx.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1));
        mnlprocess(lm, &workx, &y, _state);
        dy.ptr.p_double[0] = xy->ptr.pp_double[i][nvars];
        dserraccumulate(&buf, &y, &dy, _state);
    }
    dserrfinish(&buf, _state);
    *relcls = buf.ptr.p_double[0];
    *avgce = buf.ptr.p_double[1];
    *rms = buf.ptr.p_double[2];
    *avg = buf.ptr.p_double[3];
    *avgrel = buf.ptr.p_double[4];
    ae_frame_leave(_state);
}


/*************************************************************************
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
*************************************************************************/
static void logit_mnlmcsrch(ae_int_t n,
     /* Real    */ ae_vector* x,
     double* f,
     /* Real    */ ae_vector* g,
     /* Real    */ ae_vector* s,
     double* stp,
     ae_int_t* info,
     ae_int_t* nfev,
     /* Real    */ ae_vector* wa,
     logitmcstate* state,
     ae_int_t* stage,
     ae_state *_state)
{
    double v;
    double p5;
    double p66;
    double zero;


    
    /*
     * init
     */
    p5 = 0.5;
    p66 = 0.66;
    state->xtrapf = 4.0;
    zero = 0;
    
    /*
     * Main cycle
     */
    for(;;)
    {
        if( *stage==0 )
        {
            
            /*
             * NEXT
             */
            *stage = 2;
            continue;
        }
        if( *stage==2 )
        {
            state->infoc = 1;
            *info = 0;
            
            /*
             *     CHECK THE INPUT PARAMETERS FOR ERRORS.
             */
            if( ((((((n<=0||ae_fp_less_eq(*stp,0))||ae_fp_less(logit_ftol,0))||ae_fp_less(logit_gtol,zero))||ae_fp_less(logit_xtol,zero))||ae_fp_less(logit_stpmin,zero))||ae_fp_less(logit_stpmax,logit_stpmin))||logit_maxfev<=0 )
            {
                *stage = 0;
                return;
            }
            
            /*
             *     COMPUTE THE INITIAL GRADIENT IN THE SEARCH DIRECTION
             *     AND CHECK THAT S IS A DESCENT DIRECTION.
             */
            v = ae_v_dotproduct(&g->ptr.p_double[0], 1, &s->ptr.p_double[0], 1, ae_v_len(0,n-1));
            state->dginit = v;
            if( ae_fp_greater_eq(state->dginit,0) )
            {
                *stage = 0;
                return;
            }
            
            /*
             *     INITIALIZE LOCAL VARIABLES.
             */
            state->brackt = ae_false;
            state->stage1 = ae_true;
            *nfev = 0;
            state->finit = *f;
            state->dgtest = logit_ftol*state->dginit;
            state->width = logit_stpmax-logit_stpmin;
            state->width1 = state->width/p5;
            ae_v_move(&wa->ptr.p_double[0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,n-1));
            
            /*
             *     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->stx = 0;
            state->fx = state->finit;
            state->dgx = state->dginit;
            state->sty = 0;
            state->fy = state->finit;
            state->dgy = state->dginit;
            
            /*
             * NEXT
             */
            *stage = 3;
            continue;
        }
        if( *stage==3 )
        {
            
            /*
             *     START OF ITERATION.
             *
             *     SET THE MINIMUM AND MAXIMUM STEPS TO CORRESPOND
             *     TO THE PRESENT INTERVAL OF UNCERTAINTY.
             */
            if( state->brackt )
            {
                if( ae_fp_less(state->stx,state->sty) )
                {
                    state->stmin = state->stx;
                    state->stmax = state->sty;
                }
                else
                {
                    state->stmin = state->sty;
                    state->stmax = state->stx;
                }
            }
            else
            {
                state->stmin = state->stx;
                state->stmax = *stp+state->xtrapf*(*stp-state->stx);
            }
            
            /*
             *        FORCE THE STEP TO BE WITHIN THE BOUNDS STPMAX AND STPMIN.
             */
            if( ae_fp_greater(*stp,logit_stpmax) )
            {
                *stp = logit_stpmax;
            }
            if( ae_fp_less(*stp,logit_stpmin) )
            {
                *stp = logit_stpmin;
            }
            
            /*
             *        IF AN UNUSUAL TERMINATION IS TO OCCUR THEN LET
             *        STP BE THE LOWEST POINT OBTAINED SO FAR.
             */
            if( (((state->brackt&&(ae_fp_less_eq(*stp,state->stmin)||ae_fp_greater_eq(*stp,state->stmax)))||*nfev>=logit_maxfev-1)||state->infoc==0)||(state->brackt&&ae_fp_less_eq(state->stmax-state->stmin,logit_xtol*state->stmax)) )
            {
                *stp = state->stx;
            }
            
            /*
             *        EVALUATE THE FUNCTION AND GRADIENT AT STP
             *        AND COMPUTE THE DIRECTIONAL DERIVATIVE.
             */
            ae_v_move(&x->ptr.p_double[0], 1, &wa->ptr.p_double[0], 1, ae_v_len(0,n-1));
            ae_v_addd(&x->ptr.p_double[0], 1, &s->ptr.p_double[0], 1, ae_v_len(0,n-1), *stp);
            
            /*
             * NEXT
             */
            *stage = 4;
            return;
        }
        if( *stage==4 )
        {
            *info = 0;
            *nfev = *nfev+1;
            v = ae_v_dotproduct(&g->ptr.p_double[0], 1, &s->ptr.p_double[0], 1, ae_v_len(0,n-1));
            state->dg = v;
            state->ftest1 = state->finit+*stp*state->dgtest;
            
            /*
             *        TEST FOR CONVERGENCE.
             */
            if( (state->brackt&&(ae_fp_less_eq(*stp,state->stmin)||ae_fp_greater_eq(*stp,state->stmax)))||state->infoc==0 )
            {
                *info = 6;
            }
            if( (ae_fp_eq(*stp,logit_stpmax)&&ae_fp_less_eq(*f,state->ftest1))&&ae_fp_less_eq(state->dg,state->dgtest) )
            {
                *info = 5;
            }
            if( ae_fp_eq(*stp,logit_stpmin)&&(ae_fp_greater(*f,state->ftest1)||ae_fp_greater_eq(state->dg,state->dgtest)) )
            {
                *info = 4;
            }
            if( *nfev>=logit_maxfev )
            {
                *info = 3;
            }
            if( state->brackt&&ae_fp_less_eq(state->stmax-state->stmin,logit_xtol*state->stmax) )
            {
                *info = 2;
            }
            if( ae_fp_less_eq(*f,state->ftest1)&&ae_fp_less_eq(ae_fabs(state->dg, _state),-logit_gtol*state->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->stage1&&ae_fp_less_eq(*f,state->ftest1))&&ae_fp_greater_eq(state->dg,ae_minreal(logit_ftol, logit_gtol, _state)*state->dginit) )
            {
                state->stage1 = ae_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->stage1&&ae_fp_less_eq(*f,state->fx))&&ae_fp_greater(*f,state->ftest1) )
            {
                
                /*
                 *           DEFINE THE MODIFIED FUNCTION AND DERIVATIVE VALUES.
                 */
                state->fm = *f-*stp*state->dgtest;
                state->fxm = state->fx-state->stx*state->dgtest;
                state->fym = state->fy-state->sty*state->dgtest;
                state->dgm = state->dg-state->dgtest;
                state->dgxm = state->dgx-state->dgtest;
                state->dgym = state->dgy-state->dgtest;
                
                /*
                 *           CALL CSTEP TO UPDATE THE INTERVAL OF UNCERTAINTY
                 *           AND TO COMPUTE THE NEW STEP.
                 */
                logit_mnlmcstep(&state->stx, &state->fxm, &state->dgxm, &state->sty, &state->fym, &state->dgym, stp, state->fm, state->dgm, &state->brackt, state->stmin, state->stmax, &state->infoc, _state);
                
                /*
                 *           RESET THE FUNCTION AND GRADIENT VALUES FOR F.
                 */
                state->fx = state->fxm+state->stx*state->dgtest;
                state->fy = state->fym+state->sty*state->dgtest;
                state->dgx = state->dgxm+state->dgtest;
                state->dgy = state->dgym+state->dgtest;
            }
            else
            {
                
                /*
                 *           CALL MCSTEP TO UPDATE THE INTERVAL OF UNCERTAINTY
                 *           AND TO COMPUTE THE NEW STEP.
                 */
                logit_mnlmcstep(&state->stx, &state->fx, &state->dgx, &state->sty, &state->fy, &state->dgy, stp, *f, state->dg, &state->brackt, state->stmin, state->stmax, &state->infoc, _state);
            }
            
            /*
             *        FORCE A SUFFICIENT DECREASE IN THE SIZE OF THE
             *        INTERVAL OF UNCERTAINTY.
             */
            if( state->brackt )
            {
                if( ae_fp_greater_eq(ae_fabs(state->sty-state->stx, _state),p66*state->width1) )
                {
                    *stp = state->stx+p5*(state->sty-state->stx);
                }
                state->width1 = state->width;
                state->width = ae_fabs(state->sty-state->stx, _state);
            }
            
            /*
             *  NEXT.
             */
            *stage = 3;
            continue;
        }
    }
}


static void logit_mnlmcstep(double* stx,
     double* fx,
     double* dx,
     double* sty,
     double* fy,
     double* dy,
     double* stp,
     double fp,
     double dp,
     ae_bool* brackt,
     double stmin,
     double stmax,
     ae_int_t* info,
     ae_state *_state)
{
    ae_bool bound;
    double gamma;
    double p;
    double q;
    double r;
    double s;
    double sgnd;
    double stpc;
    double stpf;
    double stpq;
    double theta;


    *info = 0;
    
    /*
     *     CHECK THE INPUT PARAMETERS FOR ERRORS.
     */
    if( ((*brackt&&(ae_fp_less_eq(*stp,ae_minreal(*stx, *sty, _state))||ae_fp_greater_eq(*stp,ae_maxreal(*stx, *sty, _state))))||ae_fp_greater_eq(*dx*(*stp-(*stx)),0))||ae_fp_less(stmax,stmin) )
    {
        return;
    }
    
    /*
     *     DETERMINE IF THE DERIVATIVES HAVE OPPOSITE SIGN.
     */
    sgnd = dp*(*dx/ae_fabs(*dx, _state));
    
    /*
     *     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( ae_fp_greater(fp,*fx) )
    {
        *info = 1;
        bound = ae_true;
        theta = 3*(*fx-fp)/(*stp-(*stx))+(*dx)+dp;
        s = ae_maxreal(ae_fabs(theta, _state), ae_maxreal(ae_fabs(*dx, _state), ae_fabs(dp, _state), _state), _state);
        gamma = s*ae_sqrt(ae_sqr(theta/s, _state)-*dx/s*(dp/s), _state);
        if( ae_fp_less(*stp,*stx) )
        {
            gamma = -gamma;
        }
        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));
        if( ae_fp_less(ae_fabs(stpc-(*stx), _state),ae_fabs(stpq-(*stx), _state)) )
        {
            stpf = stpc;
        }
        else
        {
            stpf = stpc+(stpq-stpc)/2;
        }
        *brackt = ae_true;
    }
    else
    {
        if( ae_fp_less(sgnd,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 = ae_false;
            theta = 3*(*fx-fp)/(*stp-(*stx))+(*dx)+dp;
            s = ae_maxreal(ae_fabs(theta, _state), ae_maxreal(ae_fabs(*dx, _state), ae_fabs(dp, _state), _state), _state);
            gamma = s*ae_sqrt(ae_sqr(theta/s, _state)-*dx/s*(dp/s), _state);
            if( ae_fp_greater(*stp,*stx) )
            {
                gamma = -gamma;
            }
            p = gamma-dp+theta;
            q = gamma-dp+gamma+(*dx);
            r = p/q;
            stpc = *stp+r*(*stx-(*stp));
            stpq = *stp+dp/(dp-(*dx))*(*stx-(*stp));
            if( ae_fp_greater(ae_fabs(stpc-(*stp), _state),ae_fabs(stpq-(*stp), _state)) )
            {
                stpf = stpc;
            }
            else
            {
                stpf = stpq;
            }
            *brackt = ae_true;
        }
        else
        {
            if( ae_fp_less(ae_fabs(dp, _state),ae_fabs(*dx, _state)) )
            {
                
                /*
                 *     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 = ae_true;
                theta = 3*(*fx-fp)/(*stp-(*stx))+(*dx)+dp;
                s = ae_maxreal(ae_fabs(theta, _state), ae_maxreal(ae_fabs(*dx, _state), ae_fabs(dp, _state), _state), _state);
                
                /*
                 *        THE CASE GAMMA = 0 ONLY ARISES IF THE CUBIC DOES NOT TEND
                 *        TO INFINITY IN THE DIRECTION OF THE STEP.
                 */
                gamma = s*ae_sqrt(ae_maxreal(0, ae_sqr(theta/s, _state)-*dx/s*(dp/s), _state), _state);
                if( ae_fp_greater(*stp,*stx) )
                {
                    gamma = -gamma;
                }
                p = gamma-dp+theta;
                q = gamma+(*dx-dp)+gamma;
                r = p/q;
                if( ae_fp_less(r,0)&&ae_fp_neq(gamma,0) )
                {
                    stpc = *stp+r*(*stx-(*stp));
                }
                else
                {
                    if( ae_fp_greater(*stp,*stx) )
                    {
                        stpc = stmax;
                    }
                    else
                    {
                        stpc = stmin;
                    }
                }
                stpq = *stp+dp/(dp-(*dx))*(*stx-(*stp));
                if( *brackt )
                {
                    if( ae_fp_less(ae_fabs(*stp-stpc, _state),ae_fabs(*stp-stpq, _state)) )
                    {
                        stpf = stpc;
                    }
                    else
                    {
                        stpf = stpq;
                    }
                }
                else
                {
                    if( ae_fp_greater(ae_fabs(*stp-stpc, _state),ae_fabs(*stp-stpq, _state)) )
                    {
                        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 = ae_false;
                if( *brackt )
                {
                    theta = 3*(fp-(*fy))/(*sty-(*stp))+(*dy)+dp;
                    s = ae_maxreal(ae_fabs(theta, _state), ae_maxreal(ae_fabs(*dy, _state), ae_fabs(dp, _state), _state), _state);
                    gamma = s*ae_sqrt(ae_sqr(theta/s, _state)-*dy/s*(dp/s), _state);
                    if( ae_fp_greater(*stp,*sty) )
                    {
                        gamma = -gamma;
                    }
                    p = gamma-dp+theta;
                    q = gamma-dp+gamma+(*dy);
                    r = p/q;
                    stpc = *stp+r*(*sty-(*stp));
                    stpf = stpc;
                }
                else
                {
                    if( ae_fp_greater(*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( ae_fp_greater(fp,*fx) )
    {
        *sty = *stp;
        *fy = fp;
        *dy = dp;
    }
    else
    {
        if( ae_fp_less(sgnd,0.0) )
        {
            *sty = *stx;
            *fy = *fx;
            *dy = *dx;
        }
        *stx = *stp;
        *fx = fp;
        *dx = dp;
    }
    
    /*
     *     COMPUTE THE NEW STEP AND SAFEGUARD IT.
     */
    stpf = ae_minreal(stmax, stpf, _state);
    stpf = ae_maxreal(stmin, stpf, _state);
    *stp = stpf;
    if( *brackt&&bound )
    {
        if( ae_fp_greater(*sty,*stx) )
        {
            *stp = ae_minreal(*stx+0.66*(*sty-(*stx)), *stp, _state);
        }
        else
        {
            *stp = ae_maxreal(*stx+0.66*(*sty-(*stx)), *stp, _state);
        }
    }
}


ae_bool _logitmodel_init(void* _p, ae_state *_state, ae_bool make_automatic)
{
    logitmodel *p = (logitmodel*)_p;
    ae_touch_ptr((void*)p);
    if( !ae_vector_init(&p->w, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    return ae_true;
}


ae_bool _logitmodel_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic)
{
    logitmodel *dst = (logitmodel*)_dst;
    logitmodel *src = (logitmodel*)_src;
    if( !ae_vector_init_copy(&dst->w, &src->w, _state, make_automatic) )
        return ae_false;
    return ae_true;
}


void _logitmodel_clear(void* _p)
{
    logitmodel *p = (logitmodel*)_p;
    ae_touch_ptr((void*)p);
    ae_vector_clear(&p->w);
}


void _logitmodel_destroy(void* _p)
{
    logitmodel *p = (logitmodel*)_p;
    ae_touch_ptr((void*)p);
    ae_vector_destroy(&p->w);
}


ae_bool _logitmcstate_init(void* _p, ae_state *_state, ae_bool make_automatic)
{
    logitmcstate *p = (logitmcstate*)_p;
    ae_touch_ptr((void*)p);
    return ae_true;
}


ae_bool _logitmcstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic)
{
    logitmcstate *dst = (logitmcstate*)_dst;
    logitmcstate *src = (logitmcstate*)_src;
    dst->brackt = src->brackt;
    dst->stage1 = src->stage1;
    dst->infoc = src->infoc;
    dst->dg = src->dg;
    dst->dgm = src->dgm;
    dst->dginit = src->dginit;
    dst->dgtest = src->dgtest;
    dst->dgx = src->dgx;
    dst->dgxm = src->dgxm;
    dst->dgy = src->dgy;
    dst->dgym = src->dgym;
    dst->finit = src->finit;
    dst->ftest1 = src->ftest1;
    dst->fm = src->fm;
    dst->fx = src->fx;
    dst->fxm = src->fxm;
    dst->fy = src->fy;
    dst->fym = src->fym;
    dst->stx = src->stx;
    dst->sty = src->sty;
    dst->stmin = src->stmin;
    dst->stmax = src->stmax;
    dst->width = src->width;
    dst->width1 = src->width1;
    dst->xtrapf = src->xtrapf;
    return ae_true;
}


void _logitmcstate_clear(void* _p)
{
    logitmcstate *p = (logitmcstate*)_p;
    ae_touch_ptr((void*)p);
}


void _logitmcstate_destroy(void* _p)
{
    logitmcstate *p = (logitmcstate*)_p;
    ae_touch_ptr((void*)p);
}


ae_bool _mnlreport_init(void* _p, ae_state *_state, ae_bool make_automatic)
{
    mnlreport *p = (mnlreport*)_p;
    ae_touch_ptr((void*)p);
    return ae_true;
}


ae_bool _mnlreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic)
{
    mnlreport *dst = (mnlreport*)_dst;
    mnlreport *src = (mnlreport*)_src;
    dst->ngrad = src->ngrad;
    dst->nhess = src->nhess;
    return ae_true;
}


void _mnlreport_clear(void* _p)
{
    mnlreport *p = (mnlreport*)_p;
    ae_touch_ptr((void*)p);
}


void _mnlreport_destroy(void* _p)
{
    mnlreport *p = (mnlreport*)_p;
    ae_touch_ptr((void*)p);
}




/*************************************************************************
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  P
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

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
void mcpdcreate(ae_int_t n, mcpdstate* s, ae_state *_state)
{

    _mcpdstate_clear(s);

    ae_assert(n>=1, "MCPDCreate: N<1", _state);
    mcpd_mcpdinit(n, -1, -1, s, _state);
}


/*************************************************************************
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

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
void mcpdcreateentry(ae_int_t n,
     ae_int_t entrystate,
     mcpdstate* s,
     ae_state *_state)
{

    _mcpdstate_clear(s);

    ae_assert(n>=2, "MCPDCreateEntry: N<2", _state);
    ae_assert(entrystate>=0, "MCPDCreateEntry: EntryState<0", _state);
    ae_assert(entrystate<n, "MCPDCreateEntry: EntryState>=N", _state);
    mcpd_mcpdinit(n, entrystate, -1, s, _state);
}


/*************************************************************************
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

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
void mcpdcreateexit(ae_int_t n,
     ae_int_t exitstate,
     mcpdstate* s,
     ae_state *_state)
{

    _mcpdstate_clear(s);

    ae_assert(n>=2, "MCPDCreateExit: N<2", _state);
    ae_assert(exitstate>=0, "MCPDCreateExit: ExitState<0", _state);
    ae_assert(exitstate<n, "MCPDCreateExit: ExitState>=N", _state);
    mcpd_mcpdinit(n, -1, exitstate, s, _state);
}


/*************************************************************************
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 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
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

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
void mcpdcreateentryexit(ae_int_t n,
     ae_int_t entrystate,
     ae_int_t exitstate,
     mcpdstate* s,
     ae_state *_state)
{

    _mcpdstate_clear(s);

    ae_assert(n>=2, "MCPDCreateEntryExit: N<2", _state);
    ae_assert(entrystate>=0, "MCPDCreateEntryExit: EntryState<0", _state);
    ae_assert(entrystate<n, "MCPDCreateEntryExit: EntryState>=N", _state);
    ae_assert(exitstate>=0, "MCPDCreateEntryExit: ExitState<0", _state);
    ae_assert(exitstate<n, "MCPDCreateEntryExit: ExitState>=N", _state);
    ae_assert(entrystate!=exitstate, "MCPDCreateEntryExit: EntryState=ExitState", _state);
    mcpd_mcpdinit(n, entrystate, exitstate, s, _state);
}


/*************************************************************************
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)

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
void mcpdaddtrack(mcpdstate* s,
     /* Real    */ ae_matrix* xy,
     ae_int_t k,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t j;
    ae_int_t n;
    double s0;
    double s1;


    n = s->n;
    ae_assert(k>=0, "MCPDAddTrack: K<0", _state);
    ae_assert(xy->cols>=n, "MCPDAddTrack: Cols(XY)<N", _state);
    ae_assert(xy->rows>=k, "MCPDAddTrack: Rows(XY)<K", _state);
    ae_assert(apservisfinitematrix(xy, k, n, _state), "MCPDAddTrack: XY contains infinite or NaN elements", _state);
    for(i=0; i<=k-1; i++)
    {
        for(j=0; j<=n-1; j++)
        {
            ae_assert(ae_fp_greater_eq(xy->ptr.pp_double[i][j],0), "MCPDAddTrack: XY contains negative elements", _state);
        }
    }
    if( k<2 )
    {
        return;
    }
    if( s->data.rows<s->npairs+k-1 )
    {
        rmatrixresize(&s->data, ae_maxint(2*s->data.rows, s->npairs+k-1, _state), 2*n, _state);
    }
    for(i=0; i<=k-2; i++)
    {
        s0 = 0;
        s1 = 0;
        for(j=0; j<=n-1; j++)
        {
            if( s->states.ptr.p_int[j]>=0 )
            {
                s0 = s0+xy->ptr.pp_double[i][j];
            }
            if( s->states.ptr.p_int[j]<=0 )
            {
                s1 = s1+xy->ptr.pp_double[i+1][j];
            }
        }
        if( ae_fp_greater(s0,0)&&ae_fp_greater(s1,0) )
        {
            for(j=0; j<=n-1; j++)
            {
                if( s->states.ptr.p_int[j]>=0 )
                {
                    s->data.ptr.pp_double[s->npairs][j] = xy->ptr.pp_double[i][j]/s0;
                }
                else
                {
                    s->data.ptr.pp_double[s->npairs][j] = 0.0;
                }
                if( s->states.ptr.p_int[j]<=0 )
                {
                    s->data.ptr.pp_double[s->npairs][n+j] = xy->ptr.pp_double[i+1][j]/s1;
                }
                else
                {
                    s->data.ptr.pp_double[s->npairs][n+j] = 0.0;
                }
            }
            s->npairs = s->npairs+1;
        }
    }
}


/*************************************************************************
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().

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
void mcpdsetec(mcpdstate* s,
     /* Real    */ ae_matrix* ec,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t j;
    ae_int_t n;


    n = s->n;
    ae_assert(ec->cols>=n, "MCPDSetEC: Cols(EC)<N", _state);
    ae_assert(ec->rows>=n, "MCPDSetEC: Rows(EC)<N", _state);
    for(i=0; i<=n-1; i++)
    {
        for(j=0; j<=n-1; j++)
        {
            ae_assert(ae_isfinite(ec->ptr.pp_double[i][j], _state)||ae_isnan(ec->ptr.pp_double[i][j], _state), "MCPDSetEC: EC containts infinite elements", _state);
            s->ec.ptr.pp_double[i][j] = ec->ptr.pp_double[i][j];
        }
    }
}


/*************************************************************************
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().

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
void mcpdaddec(mcpdstate* s,
     ae_int_t i,
     ae_int_t j,
     double c,
     ae_state *_state)
{


    ae_assert(i>=0, "MCPDAddEC: I<0", _state);
    ae_assert(i<s->n, "MCPDAddEC: I>=N", _state);
    ae_assert(j>=0, "MCPDAddEC: J<0", _state);
    ae_assert(j<s->n, "MCPDAddEC: J>=N", _state);
    ae_assert(ae_isnan(c, _state)||ae_isfinite(c, _state), "MCPDAddEC: C is not finite number or NAN", _state);
    s->ec.ptr.pp_double[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.

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
void mcpdsetbc(mcpdstate* s,
     /* Real    */ ae_matrix* bndl,
     /* Real    */ ae_matrix* bndu,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t j;
    ae_int_t n;


    n = s->n;
    ae_assert(bndl->cols>=n, "MCPDSetBC: Cols(BndL)<N", _state);
    ae_assert(bndl->rows>=n, "MCPDSetBC: Rows(BndL)<N", _state);
    ae_assert(bndu->cols>=n, "MCPDSetBC: Cols(BndU)<N", _state);
    ae_assert(bndu->rows>=n, "MCPDSetBC: Rows(BndU)<N", _state);
    for(i=0; i<=n-1; i++)
    {
        for(j=0; j<=n-1; j++)
        {
            ae_assert(ae_isfinite(bndl->ptr.pp_double[i][j], _state)||ae_isneginf(bndl->ptr.pp_double[i][j], _state), "MCPDSetBC: BndL containts NAN or +INF", _state);
            ae_assert(ae_isfinite(bndu->ptr.pp_double[i][j], _state)||ae_isposinf(bndu->ptr.pp_double[i][j], _state), "MCPDSetBC: BndU containts NAN or -INF", _state);
            s->bndl.ptr.pp_double[i][j] = bndl->ptr.pp_double[i][j];
            s->bndu.ptr.pp_double[i][j] = bndu->ptr.pp_double[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

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
void mcpdaddbc(mcpdstate* s,
     ae_int_t i,
     ae_int_t j,
     double bndl,
     double bndu,
     ae_state *_state)
{


    ae_assert(i>=0, "MCPDAddBC: I<0", _state);
    ae_assert(i<s->n, "MCPDAddBC: I>=N", _state);
    ae_assert(j>=0, "MCPDAddBC: J<0", _state);
    ae_assert(j<s->n, "MCPDAddBC: J>=N", _state);
    ae_assert(ae_isfinite(bndl, _state)||ae_isneginf(bndl, _state), "MCPDAddBC: BndL is NAN or +INF", _state);
    ae_assert(ae_isfinite(bndu, _state)||ae_isposinf(bndu, _state), "MCPDAddBC: BndU is NAN or -INF", _state);
    s->bndl.ptr.pp_double[i][j] = bndl;
    s->bndu.ptr.pp_double[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

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
void mcpdsetlc(mcpdstate* s,
     /* Real    */ ae_matrix* c,
     /* Integer */ ae_vector* ct,
     ae_int_t k,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t j;
    ae_int_t n;


    n = s->n;
    ae_assert(c->cols>=n*n+1, "MCPDSetLC: Cols(C)<N*N+1", _state);
    ae_assert(c->rows>=k, "MCPDSetLC: Rows(C)<K", _state);
    ae_assert(ct->cnt>=k, "MCPDSetLC: Len(CT)<K", _state);
    ae_assert(apservisfinitematrix(c, k, n*n+1, _state), "MCPDSetLC: C contains infinite or NaN values!", _state);
    rmatrixsetlengthatleast(&s->c, k, n*n+1, _state);
    ivectorsetlengthatleast(&s->ct, k, _state);
    for(i=0; i<=k-1; i++)
    {
        for(j=0; j<=n*n; j++)
        {
            s->c.ptr.pp_double[i][j] = c->ptr.pp_double[i][j];
        }
        s->ct.ptr.p_int[i] = ct->ptr.p_int[i];
    }
    s->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.

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
void mcpdsettikhonovregularizer(mcpdstate* s, double v, ae_state *_state)
{


    ae_assert(ae_isfinite(v, _state), "MCPDSetTikhonovRegularizer: V is infinite or NAN", _state);
    ae_assert(ae_fp_greater_eq(v,0.0), "MCPDSetTikhonovRegularizer: V is less than zero", _state);
    s->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.

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
void mcpdsetprior(mcpdstate* s,
     /* Real    */ ae_matrix* pp,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_matrix _pp;
    ae_int_t i;
    ae_int_t j;
    ae_int_t n;

    ae_frame_make(_state, &_frame_block);
    ae_matrix_init_copy(&_pp, pp, _state, ae_true);
    pp = &_pp;

    n = s->n;
    ae_assert(pp->cols>=n, "MCPDSetPrior: Cols(PP)<N", _state);
    ae_assert(pp->rows>=n, "MCPDSetPrior: Rows(PP)<K", _state);
    for(i=0; i<=n-1; i++)
    {
        for(j=0; j<=n-1; j++)
        {
            ae_assert(ae_isfinite(pp->ptr.pp_double[i][j], _state), "MCPDSetPrior: PP containts infinite elements", _state);
            ae_assert(ae_fp_greater_eq(pp->ptr.pp_double[i][j],0.0)&&ae_fp_less_eq(pp->ptr.pp_double[i][j],1.0), "MCPDSetPrior: PP[i,j] is less than 0.0 or greater than 1.0", _state);
            s->priorp.ptr.pp_double[i][j] = pp->ptr.pp_double[i][j];
        }
    }
    ae_frame_leave(_state);
}


/*************************************************************************
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

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
void mcpdsetpredictionweights(mcpdstate* s,
     /* Real    */ ae_vector* pw,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t n;


    n = s->n;
    ae_assert(pw->cnt>=n, "MCPDSetPredictionWeights: Length(PW)<N", _state);
    for(i=0; i<=n-1; i++)
    {
        ae_assert(ae_isfinite(pw->ptr.p_double[i], _state), "MCPDSetPredictionWeights: PW containts infinite or NAN elements", _state);
        ae_assert(ae_fp_greater_eq(pw->ptr.p_double[i],0), "MCPDSetPredictionWeights: PW containts negative elements", _state);
        s->pw.ptr.p_double[i] = pw->ptr.p_double[i];
    }
}


/*************************************************************************
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.

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
void mcpdsolve(mcpdstate* s, ae_state *_state)
{
    ae_int_t n;
    ae_int_t npairs;
    ae_int_t ccnt;
    ae_int_t i;
    ae_int_t j;
    ae_int_t k;
    ae_int_t k2;
    double v;
    double vv;


    n = s->n;
    npairs = s->npairs;
    
    /*
     * init fields of S
     */
    s->repterminationtype = 0;
    s->repinneriterationscount = 0;
    s->repouteriterationscount = 0;
    s->repnfev = 0;
    for(k=0; k<=n-1; k++)
    {
        for(k2=0; k2<=n-1; k2++)
        {
            s->p.ptr.pp_double[k][k2] = _state->v_nan;
        }
    }
    
    /*
     * Generate "effective" weights for prediction and calculate preconditioner
     */
    for(i=0; i<=n-1; i++)
    {
        if( ae_fp_eq(s->pw.ptr.p_double[i],0) )
        {
            v = 0;
            k = 0;
            for(j=0; j<=npairs-1; j++)
            {
                if( ae_fp_neq(s->data.ptr.pp_double[j][n+i],0) )
                {
                    v = v+s->data.ptr.pp_double[j][n+i];
                    k = k+1;
                }
            }
            if( k!=0 )
            {
                s->effectivew.ptr.p_double[i] = k/v;
            }
            else
            {
                s->effectivew.ptr.p_double[i] = 1.0;
            }
        }
        else
        {
            s->effectivew.ptr.p_double[i] = s->pw.ptr.p_double[i];
        }
    }
    for(i=0; i<=n-1; i++)
    {
        for(j=0; j<=n-1; j++)
        {
            s->h.ptr.p_double[i*n+j] = 2*s->regterm;
        }
    }
    for(k=0; k<=npairs-1; k++)
    {
        for(i=0; i<=n-1; i++)
        {
            for(j=0; j<=n-1; j++)
            {
                s->h.ptr.p_double[i*n+j] = s->h.ptr.p_double[i*n+j]+2*ae_sqr(s->effectivew.ptr.p_double[i], _state)*ae_sqr(s->data.ptr.pp_double[k][j], _state);
            }
        }
    }
    for(i=0; i<=n-1; i++)
    {
        for(j=0; j<=n-1; j++)
        {
            if( ae_fp_eq(s->h.ptr.p_double[i*n+j],0) )
            {
                s->h.ptr.p_double[i*n+j] = 1;
            }
        }
    }
    
    /*
     * Generate "effective" BndL/BndU
     */
    for(i=0; i<=n-1; i++)
    {
        for(j=0; j<=n-1; j++)
        {
            
            /*
             * Set default boundary constraints.
             * Lower bound is always zero, upper bound is calculated
             * with respect to entry/exit states.
             */
            s->effectivebndl.ptr.p_double[i*n+j] = 0.0;
            if( s->states.ptr.p_int[i]>0||s->states.ptr.p_int[j]<0 )
            {
                s->effectivebndu.ptr.p_double[i*n+j] = 0.0;
            }
            else
            {
                s->effectivebndu.ptr.p_double[i*n+j] = 1.0;
            }
            
            /*
             * Calculate intersection of the default and user-specified bound constraints.
             * This code checks consistency of such combination.
             */
            if( ae_isfinite(s->bndl.ptr.pp_double[i][j], _state)&&ae_fp_greater(s->bndl.ptr.pp_double[i][j],s->effectivebndl.ptr.p_double[i*n+j]) )
            {
                s->effectivebndl.ptr.p_double[i*n+j] = s->bndl.ptr.pp_double[i][j];
            }
            if( ae_isfinite(s->bndu.ptr.pp_double[i][j], _state)&&ae_fp_less(s->bndu.ptr.pp_double[i][j],s->effectivebndu.ptr.p_double[i*n+j]) )
            {
                s->effectivebndu.ptr.p_double[i*n+j] = s->bndu.ptr.pp_double[i][j];
            }
            if( ae_fp_greater(s->effectivebndl.ptr.p_double[i*n+j],s->effectivebndu.ptr.p_double[i*n+j]) )
            {
                s->repterminationtype = -3;
                return;
            }
            
            /*
             * Calculate intersection of the effective bound constraints
             * and user-specified equality constraints.
             * This code checks consistency of such combination.
             */
            if( ae_isfinite(s->ec.ptr.pp_double[i][j], _state) )
            {
                if( ae_fp_less(s->ec.ptr.pp_double[i][j],s->effectivebndl.ptr.p_double[i*n+j])||ae_fp_greater(s->ec.ptr.pp_double[i][j],s->effectivebndu.ptr.p_double[i*n+j]) )
                {
                    s->repterminationtype = -3;
                    return;
                }
                s->effectivebndl.ptr.p_double[i*n+j] = s->ec.ptr.pp_double[i][j];
                s->effectivebndu.ptr.p_double[i*n+j] = s->ec.ptr.pp_double[i][j];
            }
        }
    }
    
    /*
     * Generate linear constraints:
     * * "default" sums-to-one constraints (not generated for "exit" states)
     */
    rmatrixsetlengthatleast(&s->effectivec, s->ccnt+n, n*n+1, _state);
    ivectorsetlengthatleast(&s->effectivect, s->ccnt+n, _state);
    ccnt = s->ccnt;
    for(i=0; i<=s->ccnt-1; i++)
    {
        for(j=0; j<=n*n; j++)
        {
            s->effectivec.ptr.pp_double[i][j] = s->c.ptr.pp_double[i][j];
        }
        s->effectivect.ptr.p_int[i] = s->ct.ptr.p_int[i];
    }
    for(i=0; i<=n-1; i++)
    {
        if( s->states.ptr.p_int[i]>=0 )
        {
            for(k=0; k<=n*n-1; k++)
            {
                s->effectivec.ptr.pp_double[ccnt][k] = 0;
            }
            for(k=0; k<=n-1; k++)
            {
                s->effectivec.ptr.pp_double[ccnt][k*n+i] = 1;
            }
            s->effectivec.ptr.pp_double[ccnt][n*n] = 1.0;
            s->effectivect.ptr.p_int[ccnt] = 0;
            ccnt = ccnt+1;
        }
    }
    
    /*
     * create optimizer
     */
    for(i=0; i<=n-1; i++)
    {
        for(j=0; j<=n-1; j++)
        {
            s->tmpp.ptr.p_double[i*n+j] = (double)1/(double)n;
        }
    }
    minbleicrestartfrom(&s->bs, &s->tmpp, _state);
    minbleicsetbc(&s->bs, &s->effectivebndl, &s->effectivebndu, _state);
    minbleicsetlc(&s->bs, &s->effectivec, &s->effectivect, ccnt, _state);
    minbleicsetcond(&s->bs, 0.0, 0.0, mcpd_xtol, 0, _state);
    minbleicsetprecdiag(&s->bs, &s->h, _state);
    
    /*
     * solve problem
     */
    while(minbleiciteration(&s->bs, _state))
    {
        ae_assert(s->bs.needfg, "MCPDSolve: internal error", _state);
        if( s->bs.needfg )
        {
            
            /*
             * Calculate regularization term
             */
            s->bs.f = 0.0;
            vv = s->regterm;
            for(i=0; i<=n-1; i++)
            {
                for(j=0; j<=n-1; j++)
                {
                    s->bs.f = s->bs.f+vv*ae_sqr(s->bs.x.ptr.p_double[i*n+j]-s->priorp.ptr.pp_double[i][j], _state);
                    s->bs.g.ptr.p_double[i*n+j] = 2*vv*(s->bs.x.ptr.p_double[i*n+j]-s->priorp.ptr.pp_double[i][j]);
                }
            }
            
            /*
             * calculate prediction error/gradient for K-th pair
             */
            for(k=0; k<=npairs-1; k++)
            {
                for(i=0; i<=n-1; i++)
                {
                    v = ae_v_dotproduct(&s->bs.x.ptr.p_double[i*n], 1, &s->data.ptr.pp_double[k][0], 1, ae_v_len(i*n,i*n+n-1));
                    vv = s->effectivew.ptr.p_double[i];
                    s->bs.f = s->bs.f+ae_sqr(vv*(v-s->data.ptr.pp_double[k][n+i]), _state);
                    for(j=0; j<=n-1; j++)
                    {
                        s->bs.g.ptr.p_double[i*n+j] = s->bs.g.ptr.p_double[i*n+j]+2*vv*vv*(v-s->data.ptr.pp_double[k][n+i])*s->data.ptr.pp_double[k][j];
                    }
                }
            }
            
            /*
             * continue
             */
            continue;
        }
    }
    minbleicresultsbuf(&s->bs, &s->tmpp, &s->br, _state);
    for(i=0; i<=n-1; i++)
    {
        for(j=0; j<=n-1; j++)
        {
            s->p.ptr.pp_double[i][j] = s->tmpp.ptr.p_double[i*n+j];
        }
    }
    s->repterminationtype = s->br.terminationtype;
    s->repinneriterationscount = s->br.inneriterationscount;
    s->repouteriterationscount = s->br.outeriterationscount;
    s->repnfev = s->br.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  be
                found in the comments on MCPDReport datatype.


  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
void mcpdresults(mcpdstate* s,
     /* Real    */ ae_matrix* p,
     mcpdreport* rep,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t j;

    ae_matrix_clear(p);
    _mcpdreport_clear(rep);

    ae_matrix_set_length(p, s->n, s->n, _state);
    for(i=0; i<=s->n-1; i++)
    {
        for(j=0; j<=s->n-1; j++)
        {
            p->ptr.pp_double[i][j] = s->p.ptr.pp_double[i][j];
        }
    }
    rep->terminationtype = s->repterminationtype;
    rep->inneriterationscount = s->repinneriterationscount;
    rep->outeriterationscount = s->repouteriterationscount;
    rep->nfev = s->repnfev;
}


/*************************************************************************
Internal initialization function

  -- ALGLIB --
     Copyright 23.05.2010 by Bochkanov Sergey
*************************************************************************/
static void mcpd_mcpdinit(ae_int_t n,
     ae_int_t entrystate,
     ae_int_t exitstate,
     mcpdstate* s,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t j;


    ae_assert(n>=1, "MCPDCreate: N<1", _state);
    s->n = n;
    ae_vector_set_length(&s->states, n, _state);
    for(i=0; i<=n-1; i++)
    {
        s->states.ptr.p_int[i] = 0;
    }
    if( entrystate>=0 )
    {
        s->states.ptr.p_int[entrystate] = 1;
    }
    if( exitstate>=0 )
    {
        s->states.ptr.p_int[exitstate] = -1;
    }
    s->npairs = 0;
    s->regterm = 1.0E-8;
    s->ccnt = 0;
    ae_matrix_set_length(&s->p, n, n, _state);
    ae_matrix_set_length(&s->ec, n, n, _state);
    ae_matrix_set_length(&s->bndl, n, n, _state);
    ae_matrix_set_length(&s->bndu, n, n, _state);
    ae_vector_set_length(&s->pw, n, _state);
    ae_matrix_set_length(&s->priorp, n, n, _state);
    ae_vector_set_length(&s->tmpp, n*n, _state);
    ae_vector_set_length(&s->effectivew, n, _state);
    ae_vector_set_length(&s->effectivebndl, n*n, _state);
    ae_vector_set_length(&s->effectivebndu, n*n, _state);
    ae_vector_set_length(&s->h, n*n, _state);
    for(i=0; i<=n-1; i++)
    {
        for(j=0; j<=n-1; j++)
        {
            s->p.ptr.pp_double[i][j] = 0.0;
            s->priorp.ptr.pp_double[i][j] = 0.0;
            s->bndl.ptr.pp_double[i][j] = _state->v_neginf;
            s->bndu.ptr.pp_double[i][j] = _state->v_posinf;
            s->ec.ptr.pp_double[i][j] = _state->v_nan;
        }
        s->pw.ptr.p_double[i] = 0.0;
        s->priorp.ptr.pp_double[i][i] = 1.0;
    }
    ae_matrix_set_length(&s->data, 1, 2*n, _state);
    for(i=0; i<=2*n-1; i++)
    {
        s->data.ptr.pp_double[0][i] = 0.0;
    }
    for(i=0; i<=n*n-1; i++)
    {
        s->tmpp.ptr.p_double[i] = 0.0;
    }
    minbleiccreate(n*n, &s->tmpp, &s->bs, _state);
}


ae_bool _mcpdstate_init(void* _p, ae_state *_state, ae_bool make_automatic)
{
    mcpdstate *p = (mcpdstate*)_p;
    ae_touch_ptr((void*)p);
    if( !ae_vector_init(&p->states, 0, DT_INT, _state, make_automatic) )
        return ae_false;
    if( !ae_matrix_init(&p->data, 0, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_matrix_init(&p->ec, 0, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_matrix_init(&p->bndl, 0, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_matrix_init(&p->bndu, 0, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_matrix_init(&p->c, 0, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->ct, 0, DT_INT, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->pw, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_matrix_init(&p->priorp, 0, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !_minbleicstate_init(&p->bs, _state, make_automatic) )
        return ae_false;
    if( !_minbleicreport_init(&p->br, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->tmpp, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->effectivew, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->effectivebndl, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->effectivebndu, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_matrix_init(&p->effectivec, 0, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->effectivect, 0, DT_INT, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->h, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_matrix_init(&p->p, 0, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    return ae_true;
}


ae_bool _mcpdstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic)
{
    mcpdstate *dst = (mcpdstate*)_dst;
    mcpdstate *src = (mcpdstate*)_src;
    dst->n = src->n;
    if( !ae_vector_init_copy(&dst->states, &src->states, _state, make_automatic) )
        return ae_false;
    dst->npairs = src->npairs;
    if( !ae_matrix_init_copy(&dst->data, &src->data, _state, make_automatic) )
        return ae_false;
    if( !ae_matrix_init_copy(&dst->ec, &src->ec, _state, make_automatic) )
        return ae_false;
    if( !ae_matrix_init_copy(&dst->bndl, &src->bndl, _state, make_automatic) )
        return ae_false;
    if( !ae_matrix_init_copy(&dst->bndu, &src->bndu, _state, make_automatic) )
        return ae_false;
    if( !ae_matrix_init_copy(&dst->c, &src->c, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->ct, &src->ct, _state, make_automatic) )
        return ae_false;
    dst->ccnt = src->ccnt;
    if( !ae_vector_init_copy(&dst->pw, &src->pw, _state, make_automatic) )
        return ae_false;
    if( !ae_matrix_init_copy(&dst->priorp, &src->priorp, _state, make_automatic) )
        return ae_false;
    dst->regterm = src->regterm;
    if( !_minbleicstate_init_copy(&dst->bs, &src->bs, _state, make_automatic) )
        return ae_false;
    dst->repinneriterationscount = src->repinneriterationscount;
    dst->repouteriterationscount = src->repouteriterationscount;
    dst->repnfev = src->repnfev;
    dst->repterminationtype = src->repterminationtype;
    if( !_minbleicreport_init_copy(&dst->br, &src->br, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->tmpp, &src->tmpp, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->effectivew, &src->effectivew, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->effectivebndl, &src->effectivebndl, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->effectivebndu, &src->effectivebndu, _state, make_automatic) )
        return ae_false;
    if( !ae_matrix_init_copy(&dst->effectivec, &src->effectivec, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->effectivect, &src->effectivect, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->h, &src->h, _state, make_automatic) )
        return ae_false;
    if( !ae_matrix_init_copy(&dst->p, &src->p, _state, make_automatic) )
        return ae_false;
    return ae_true;
}


void _mcpdstate_clear(void* _p)
{
    mcpdstate *p = (mcpdstate*)_p;
    ae_touch_ptr((void*)p);
    ae_vector_clear(&p->states);
    ae_matrix_clear(&p->data);
    ae_matrix_clear(&p->ec);
    ae_matrix_clear(&p->bndl);
    ae_matrix_clear(&p->bndu);
    ae_matrix_clear(&p->c);
    ae_vector_clear(&p->ct);
    ae_vector_clear(&p->pw);
    ae_matrix_clear(&p->priorp);
    _minbleicstate_clear(&p->bs);
    _minbleicreport_clear(&p->br);
    ae_vector_clear(&p->tmpp);
    ae_vector_clear(&p->effectivew);
    ae_vector_clear(&p->effectivebndl);
    ae_vector_clear(&p->effectivebndu);
    ae_matrix_clear(&p->effectivec);
    ae_vector_clear(&p->effectivect);
    ae_vector_clear(&p->h);
    ae_matrix_clear(&p->p);
}


void _mcpdstate_destroy(void* _p)
{
    mcpdstate *p = (mcpdstate*)_p;
    ae_touch_ptr((void*)p);
    ae_vector_destroy(&p->states);
    ae_matrix_destroy(&p->data);
    ae_matrix_destroy(&p->ec);
    ae_matrix_destroy(&p->bndl);
    ae_matrix_destroy(&p->bndu);
    ae_matrix_destroy(&p->c);
    ae_vector_destroy(&p->ct);
    ae_vector_destroy(&p->pw);
    ae_matrix_destroy(&p->priorp);
    _minbleicstate_destroy(&p->bs);
    _minbleicreport_destroy(&p->br);
    ae_vector_destroy(&p->tmpp);
    ae_vector_destroy(&p->effectivew);
    ae_vector_destroy(&p->effectivebndl);
    ae_vector_destroy(&p->effectivebndu);
    ae_matrix_destroy(&p->effectivec);
    ae_vector_destroy(&p->effectivect);
    ae_vector_destroy(&p->h);
    ae_matrix_destroy(&p->p);
}


ae_bool _mcpdreport_init(void* _p, ae_state *_state, ae_bool make_automatic)
{
    mcpdreport *p = (mcpdreport*)_p;
    ae_touch_ptr((void*)p);
    return ae_true;
}


ae_bool _mcpdreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic)
{
    mcpdreport *dst = (mcpdreport*)_dst;
    mcpdreport *src = (mcpdreport*)_src;
    dst->inneriterationscount = src->inneriterationscount;
    dst->outeriterationscount = src->outeriterationscount;
    dst->nfev = src->nfev;
    dst->terminationtype = src->terminationtype;
    return ae_true;
}


void _mcpdreport_clear(void* _p)
{
    mcpdreport *p = (mcpdreport*)_p;
    ae_touch_ptr((void*)p);
}


void _mcpdreport_destroy(void* _p)
{
    mcpdreport *p = (mcpdreport*)_p;
    ae_touch_ptr((void*)p);
}




/*************************************************************************
Like MLPCreate0, but for ensembles.

  -- ALGLIB --
     Copyright 18.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpecreate0(ae_int_t nin,
     ae_int_t nout,
     ae_int_t ensemblesize,
     mlpensemble* ensemble,
     ae_state *_state)
{
    ae_frame _frame_block;
    multilayerperceptron net;

    ae_frame_make(_state, &_frame_block);
    _mlpensemble_clear(ensemble);
    _multilayerperceptron_init(&net, _state, ae_true);

    mlpcreate0(nin, nout, &net, _state);
    mlpecreatefromnetwork(&net, ensemblesize, ensemble, _state);
    ae_frame_leave(_state);
}


/*************************************************************************
Like MLPCreate1, but for ensembles.

  -- ALGLIB --
     Copyright 18.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpecreate1(ae_int_t nin,
     ae_int_t nhid,
     ae_int_t nout,
     ae_int_t ensemblesize,
     mlpensemble* ensemble,
     ae_state *_state)
{
    ae_frame _frame_block;
    multilayerperceptron net;

    ae_frame_make(_state, &_frame_block);
    _mlpensemble_clear(ensemble);
    _multilayerperceptron_init(&net, _state, ae_true);

    mlpcreate1(nin, nhid, nout, &net, _state);
    mlpecreatefromnetwork(&net, ensemblesize, ensemble, _state);
    ae_frame_leave(_state);
}


/*************************************************************************
Like MLPCreate2, but for ensembles.

  -- ALGLIB --
     Copyright 18.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpecreate2(ae_int_t nin,
     ae_int_t nhid1,
     ae_int_t nhid2,
     ae_int_t nout,
     ae_int_t ensemblesize,
     mlpensemble* ensemble,
     ae_state *_state)
{
    ae_frame _frame_block;
    multilayerperceptron net;

    ae_frame_make(_state, &_frame_block);
    _mlpensemble_clear(ensemble);
    _multilayerperceptron_init(&net, _state, ae_true);

    mlpcreate2(nin, nhid1, nhid2, nout, &net, _state);
    mlpecreatefromnetwork(&net, ensemblesize, ensemble, _state);
    ae_frame_leave(_state);
}


/*************************************************************************
Like MLPCreateB0, but for ensembles.

  -- ALGLIB --
     Copyright 18.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpecreateb0(ae_int_t nin,
     ae_int_t nout,
     double b,
     double d,
     ae_int_t ensemblesize,
     mlpensemble* ensemble,
     ae_state *_state)
{
    ae_frame _frame_block;
    multilayerperceptron net;

    ae_frame_make(_state, &_frame_block);
    _mlpensemble_clear(ensemble);
    _multilayerperceptron_init(&net, _state, ae_true);

    mlpcreateb0(nin, nout, b, d, &net, _state);
    mlpecreatefromnetwork(&net, ensemblesize, ensemble, _state);
    ae_frame_leave(_state);
}


/*************************************************************************
Like MLPCreateB1, but for ensembles.

  -- ALGLIB --
     Copyright 18.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpecreateb1(ae_int_t nin,
     ae_int_t nhid,
     ae_int_t nout,
     double b,
     double d,
     ae_int_t ensemblesize,
     mlpensemble* ensemble,
     ae_state *_state)
{
    ae_frame _frame_block;
    multilayerperceptron net;

    ae_frame_make(_state, &_frame_block);
    _mlpensemble_clear(ensemble);
    _multilayerperceptron_init(&net, _state, ae_true);

    mlpcreateb1(nin, nhid, nout, b, d, &net, _state);
    mlpecreatefromnetwork(&net, ensemblesize, ensemble, _state);
    ae_frame_leave(_state);
}


/*************************************************************************
Like MLPCreateB2, but for ensembles.

  -- ALGLIB --
     Copyright 18.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpecreateb2(ae_int_t nin,
     ae_int_t nhid1,
     ae_int_t nhid2,
     ae_int_t nout,
     double b,
     double d,
     ae_int_t ensemblesize,
     mlpensemble* ensemble,
     ae_state *_state)
{
    ae_frame _frame_block;
    multilayerperceptron net;

    ae_frame_make(_state, &_frame_block);
    _mlpensemble_clear(ensemble);
    _multilayerperceptron_init(&net, _state, ae_true);

    mlpcreateb2(nin, nhid1, nhid2, nout, b, d, &net, _state);
    mlpecreatefromnetwork(&net, ensemblesize, ensemble, _state);
    ae_frame_leave(_state);
}


/*************************************************************************
Like MLPCreateR0, but for ensembles.

  -- ALGLIB --
     Copyright 18.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpecreater0(ae_int_t nin,
     ae_int_t nout,
     double a,
     double b,
     ae_int_t ensemblesize,
     mlpensemble* ensemble,
     ae_state *_state)
{
    ae_frame _frame_block;
    multilayerperceptron net;

    ae_frame_make(_state, &_frame_block);
    _mlpensemble_clear(ensemble);
    _multilayerperceptron_init(&net, _state, ae_true);

    mlpcreater0(nin, nout, a, b, &net, _state);
    mlpecreatefromnetwork(&net, ensemblesize, ensemble, _state);
    ae_frame_leave(_state);
}


/*************************************************************************
Like MLPCreateR1, but for ensembles.

  -- ALGLIB --
     Copyright 18.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpecreater1(ae_int_t nin,
     ae_int_t nhid,
     ae_int_t nout,
     double a,
     double b,
     ae_int_t ensemblesize,
     mlpensemble* ensemble,
     ae_state *_state)
{
    ae_frame _frame_block;
    multilayerperceptron net;

    ae_frame_make(_state, &_frame_block);
    _mlpensemble_clear(ensemble);
    _multilayerperceptron_init(&net, _state, ae_true);

    mlpcreater1(nin, nhid, nout, a, b, &net, _state);
    mlpecreatefromnetwork(&net, ensemblesize, ensemble, _state);
    ae_frame_leave(_state);
}


/*************************************************************************
Like MLPCreateR2, but for ensembles.

  -- ALGLIB --
     Copyright 18.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpecreater2(ae_int_t nin,
     ae_int_t nhid1,
     ae_int_t nhid2,
     ae_int_t nout,
     double a,
     double b,
     ae_int_t ensemblesize,
     mlpensemble* ensemble,
     ae_state *_state)
{
    ae_frame _frame_block;
    multilayerperceptron net;

    ae_frame_make(_state, &_frame_block);
    _mlpensemble_clear(ensemble);
    _multilayerperceptron_init(&net, _state, ae_true);

    mlpcreater2(nin, nhid1, nhid2, nout, a, b, &net, _state);
    mlpecreatefromnetwork(&net, ensemblesize, ensemble, _state);
    ae_frame_leave(_state);
}


/*************************************************************************
Like MLPCreateC0, but for ensembles.

  -- ALGLIB --
     Copyright 18.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpecreatec0(ae_int_t nin,
     ae_int_t nout,
     ae_int_t ensemblesize,
     mlpensemble* ensemble,
     ae_state *_state)
{
    ae_frame _frame_block;
    multilayerperceptron net;

    ae_frame_make(_state, &_frame_block);
    _mlpensemble_clear(ensemble);
    _multilayerperceptron_init(&net, _state, ae_true);

    mlpcreatec0(nin, nout, &net, _state);
    mlpecreatefromnetwork(&net, ensemblesize, ensemble, _state);
    ae_frame_leave(_state);
}


/*************************************************************************
Like MLPCreateC1, but for ensembles.

  -- ALGLIB --
     Copyright 18.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpecreatec1(ae_int_t nin,
     ae_int_t nhid,
     ae_int_t nout,
     ae_int_t ensemblesize,
     mlpensemble* ensemble,
     ae_state *_state)
{
    ae_frame _frame_block;
    multilayerperceptron net;

    ae_frame_make(_state, &_frame_block);
    _mlpensemble_clear(ensemble);
    _multilayerperceptron_init(&net, _state, ae_true);

    mlpcreatec1(nin, nhid, nout, &net, _state);
    mlpecreatefromnetwork(&net, ensemblesize, ensemble, _state);
    ae_frame_leave(_state);
}


/*************************************************************************
Like MLPCreateC2, but for ensembles.

  -- ALGLIB --
     Copyright 18.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpecreatec2(ae_int_t nin,
     ae_int_t nhid1,
     ae_int_t nhid2,
     ae_int_t nout,
     ae_int_t ensemblesize,
     mlpensemble* ensemble,
     ae_state *_state)
{
    ae_frame _frame_block;
    multilayerperceptron net;

    ae_frame_make(_state, &_frame_block);
    _mlpensemble_clear(ensemble);
    _multilayerperceptron_init(&net, _state, ae_true);

    mlpcreatec2(nin, nhid1, nhid2, nout, &net, _state);
    mlpecreatefromnetwork(&net, ensemblesize, ensemble, _state);
    ae_frame_leave(_state);
}


/*************************************************************************
Creates ensemble from network. Only network geometry is copied.

  -- ALGLIB --
     Copyright 17.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpecreatefromnetwork(multilayerperceptron* network,
     ae_int_t ensemblesize,
     mlpensemble* ensemble,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t ccount;
    ae_int_t wcount;

    _mlpensemble_clear(ensemble);

    ae_assert(ensemblesize>0, "MLPECreate: incorrect ensemble size!", _state);
    
    /*
     * Copy network
     */
    mlpcopy(network, &ensemble->network, _state);
    
    /*
     * network properties
     */
    if( mlpissoftmax(network, _state) )
    {
        ccount = mlpgetinputscount(&ensemble->network, _state);
    }
    else
    {
        ccount = mlpgetinputscount(&ensemble->network, _state)+mlpgetoutputscount(&ensemble->network, _state);
    }
    wcount = mlpgetweightscount(&ensemble->network, _state);
    ensemble->ensemblesize = ensemblesize;
    
    /*
     * weights, means, sigmas
     */
    ae_vector_set_length(&ensemble->weights, ensemblesize*wcount, _state);
    ae_vector_set_length(&ensemble->columnmeans, ensemblesize*ccount, _state);
    ae_vector_set_length(&ensemble->columnsigmas, ensemblesize*ccount, _state);
    for(i=0; i<=ensemblesize*wcount-1; i++)
    {
        ensemble->weights.ptr.p_double[i] = ae_randomreal(_state)-0.5;
    }
    for(i=0; i<=ensemblesize-1; i++)
    {
        ae_v_move(&ensemble->columnmeans.ptr.p_double[i*ccount], 1, &network->columnmeans.ptr.p_double[0], 1, ae_v_len(i*ccount,(i+1)*ccount-1));
        ae_v_move(&ensemble->columnsigmas.ptr.p_double[i*ccount], 1, &network->columnsigmas.ptr.p_double[0], 1, ae_v_len(i*ccount,(i+1)*ccount-1));
    }
    
    /*
     * temporaries, internal buffers
     */
    ae_vector_set_length(&ensemble->y, mlpgetoutputscount(&ensemble->network, _state), _state);
}


/*************************************************************************
Copying of MLPEnsemble strucure

INPUT PARAMETERS:
    Ensemble1 -   original

OUTPUT PARAMETERS:
    Ensemble2 -   copy

  -- ALGLIB --
     Copyright 17.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpecopy(mlpensemble* ensemble1,
     mlpensemble* ensemble2,
     ae_state *_state)
{
    ae_int_t ccount;
    ae_int_t wcount;

    _mlpensemble_clear(ensemble2);

    
    /*
     * Unload info
     */
    if( mlpissoftmax(&ensemble1->network, _state) )
    {
        ccount = mlpgetinputscount(&ensemble1->network, _state);
    }
    else
    {
        ccount = mlpgetinputscount(&ensemble1->network, _state)+mlpgetoutputscount(&ensemble1->network, _state);
    }
    wcount = mlpgetweightscount(&ensemble1->network, _state);
    
    /*
     * Allocate space
     */
    ae_vector_set_length(&ensemble2->weights, ensemble1->ensemblesize*wcount, _state);
    ae_vector_set_length(&ensemble2->columnmeans, ensemble1->ensemblesize*ccount, _state);
    ae_vector_set_length(&ensemble2->columnsigmas, ensemble1->ensemblesize*ccount, _state);
    ae_vector_set_length(&ensemble2->y, mlpgetoutputscount(&ensemble1->network, _state), _state);
    
    /*
     * Copy
     */
    ensemble2->ensemblesize = ensemble1->ensemblesize;
    ae_v_move(&ensemble2->weights.ptr.p_double[0], 1, &ensemble1->weights.ptr.p_double[0], 1, ae_v_len(0,ensemble1->ensemblesize*wcount-1));
    ae_v_move(&ensemble2->columnmeans.ptr.p_double[0], 1, &ensemble1->columnmeans.ptr.p_double[0], 1, ae_v_len(0,ensemble1->ensemblesize*ccount-1));
    ae_v_move(&ensemble2->columnsigmas.ptr.p_double[0], 1, &ensemble1->columnsigmas.ptr.p_double[0], 1, ae_v_len(0,ensemble1->ensemblesize*ccount-1));
    mlpcopy(&ensemble1->network, &ensemble2->network, _state);
}


/*************************************************************************
Randomization of MLP ensemble

  -- ALGLIB --
     Copyright 17.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlperandomize(mlpensemble* ensemble, ae_state *_state)
{
    ae_int_t i;
    ae_int_t wcount;


    wcount = mlpgetweightscount(&ensemble->network, _state);
    for(i=0; i<=ensemble->ensemblesize*wcount-1; i++)
    {
        ensemble->weights.ptr.p_double[i] = ae_randomreal(_state)-0.5;
    }
}


/*************************************************************************
Return ensemble properties (number of inputs and outputs).

  -- ALGLIB --
     Copyright 17.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpeproperties(mlpensemble* ensemble,
     ae_int_t* nin,
     ae_int_t* nout,
     ae_state *_state)
{

    *nin = 0;
    *nout = 0;

    *nin = mlpgetinputscount(&ensemble->network, _state);
    *nout = mlpgetoutputscount(&ensemble->network, _state);
}


/*************************************************************************
Return normalization type (whether ensemble is SOFTMAX-normalized or not).

  -- ALGLIB --
     Copyright 17.02.2009 by Bochkanov Sergey
*************************************************************************/
ae_bool mlpeissoftmax(mlpensemble* ensemble, ae_state *_state)
{
    ae_bool result;


    result = mlpissoftmax(&ensemble->network, _state);
    return result;
}


/*************************************************************************
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.

  -- ALGLIB --
     Copyright 17.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpeprocess(mlpensemble* ensemble,
     /* Real    */ ae_vector* x,
     /* Real    */ ae_vector* y,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t es;
    ae_int_t wc;
    ae_int_t cc;
    double v;
    ae_int_t nout;


    if( y->cnt<mlpgetoutputscount(&ensemble->network, _state) )
    {
        ae_vector_set_length(y, mlpgetoutputscount(&ensemble->network, _state), _state);
    }
    es = ensemble->ensemblesize;
    wc = mlpgetweightscount(&ensemble->network, _state);
    if( mlpissoftmax(&ensemble->network, _state) )
    {
        cc = mlpgetinputscount(&ensemble->network, _state);
    }
    else
    {
        cc = mlpgetinputscount(&ensemble->network, _state)+mlpgetoutputscount(&ensemble->network, _state);
    }
    v = (double)1/(double)es;
    nout = mlpgetoutputscount(&ensemble->network, _state);
    for(i=0; i<=nout-1; i++)
    {
        y->ptr.p_double[i] = 0;
    }
    for(i=0; i<=es-1; i++)
    {
        ae_v_move(&ensemble->network.weights.ptr.p_double[0], 1, &ensemble->weights.ptr.p_double[i*wc], 1, ae_v_len(0,wc-1));
        ae_v_move(&ensemble->network.columnmeans.ptr.p_double[0], 1, &ensemble->columnmeans.ptr.p_double[i*cc], 1, ae_v_len(0,cc-1));
        ae_v_move(&ensemble->network.columnsigmas.ptr.p_double[0], 1, &ensemble->columnsigmas.ptr.p_double[i*cc], 1, ae_v_len(0,cc-1));
        mlpprocess(&ensemble->network, x, &ensemble->y, _state);
        ae_v_addd(&y->ptr.p_double[0], 1, &ensemble->y.ptr.p_double[0], 1, ae_v_len(0,nout-1), v);
    }
}


/*************************************************************************
'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.

  -- ALGLIB --
     Copyright 17.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpeprocessi(mlpensemble* ensemble,
     /* Real    */ ae_vector* x,
     /* Real    */ ae_vector* y,
     ae_state *_state)
{

    ae_vector_clear(y);

    mlpeprocess(ensemble, x, y, _state);
}


/*************************************************************************
Calculation of all types of errors

  -- ALGLIB --
     Copyright 17.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpeallerrors(mlpensemble* ensemble,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     double* relcls,
     double* avgce,
     double* rms,
     double* avg,
     double* avgrel,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t i;
    ae_vector buf;
    ae_vector workx;
    ae_vector y;
    ae_vector dy;
    ae_int_t nin;
    ae_int_t nout;

    ae_frame_make(_state, &_frame_block);
    *relcls = 0;
    *avgce = 0;
    *rms = 0;
    *avg = 0;
    *avgrel = 0;
    ae_vector_init(&buf, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&workx, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&y, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&dy, 0, DT_REAL, _state, ae_true);

    nin = mlpgetinputscount(&ensemble->network, _state);
    nout = mlpgetoutputscount(&ensemble->network, _state);
    ae_vector_set_length(&workx, nin, _state);
    ae_vector_set_length(&y, nout, _state);
    if( mlpissoftmax(&ensemble->network, _state) )
    {
        ae_vector_set_length(&dy, 1, _state);
        dserrallocate(nout, &buf, _state);
    }
    else
    {
        ae_vector_set_length(&dy, nout, _state);
        dserrallocate(-nout, &buf, _state);
    }
    for(i=0; i<=npoints-1; i++)
    {
        ae_v_move(&workx.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nin-1));
        mlpeprocess(ensemble, &workx, &y, _state);
        if( mlpissoftmax(&ensemble->network, _state) )
        {
            dy.ptr.p_double[0] = xy->ptr.pp_double[i][nin];
        }
        else
        {
            ae_v_move(&dy.ptr.p_double[0], 1, &xy->ptr.pp_double[i][nin], 1, ae_v_len(0,nout-1));
        }
        dserraccumulate(&buf, &y, &dy, _state);
    }
    dserrfinish(&buf, _state);
    *relcls = buf.ptr.p_double[0];
    *avgce = buf.ptr.p_double[1];
    *rms = buf.ptr.p_double[2];
    *avg = buf.ptr.p_double[3];
    *avgrel = buf.ptr.p_double[4];
    ae_frame_leave(_state);
}


/*************************************************************************
Calculation of all types of errors on dataset given by sparse matrix

  -- ALGLIB --
     Copyright 10.09.2012 by Bochkanov Sergey
*************************************************************************/
void mlpeallerrorssparse(mlpensemble* ensemble,
     sparsematrix* xy,
     ae_int_t npoints,
     double* relcls,
     double* avgce,
     double* rms,
     double* avg,
     double* avgrel,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t i;
    ae_vector buf;
    ae_vector workx;
    ae_vector y;
    ae_vector dy;
    ae_int_t nin;
    ae_int_t nout;

    ae_frame_make(_state, &_frame_block);
    *relcls = 0;
    *avgce = 0;
    *rms = 0;
    *avg = 0;
    *avgrel = 0;
    ae_vector_init(&buf, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&workx, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&y, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&dy, 0, DT_REAL, _state, ae_true);

    nin = mlpgetinputscount(&ensemble->network, _state);
    nout = mlpgetoutputscount(&ensemble->network, _state);
    if( mlpissoftmax(&ensemble->network, _state) )
    {
        ae_vector_set_length(&dy, 1, _state);
        dserrallocate(nout, &buf, _state);
    }
    else
    {
        ae_vector_set_length(&dy, nout, _state);
        dserrallocate(-nout, &buf, _state);
    }
    for(i=0; i<=npoints-1; i++)
    {
        sparsegetrow(xy, i, &workx, _state);
        mlpeprocess(ensemble, &workx, &y, _state);
        if( mlpissoftmax(&ensemble->network, _state) )
        {
            dy.ptr.p_double[0] = workx.ptr.p_double[nin];
        }
        else
        {
            ae_v_move(&dy.ptr.p_double[0], 1, &workx.ptr.p_double[nin], 1, ae_v_len(0,nout-1));
        }
        dserraccumulate(&buf, &y, &dy, _state);
    }
    dserrfinish(&buf, _state);
    *relcls = buf.ptr.p_double[0];
    *avgce = buf.ptr.p_double[1];
    *rms = buf.ptr.p_double[2];
    *avg = buf.ptr.p_double[3];
    *avgrel = buf.ptr.p_double[4];
    ae_frame_leave(_state);
}


/*************************************************************************
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.

  -- ALGLIB --
     Copyright 17.02.2009 by Bochkanov Sergey
*************************************************************************/
double mlperelclserror(mlpensemble* ensemble,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_state *_state)
{
    double relcls;
    double avgce;
    double rms;
    double avg;
    double avgrel;
    double result;


    mlpeallerrors(ensemble, xy, npoints, &relcls, &avgce, &rms, &avg, &avgrel, _state);
    result = relcls;
    return result;
}


/*************************************************************************
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.

  -- ALGLIB --
     Copyright 17.02.2009 by Bochkanov Sergey
*************************************************************************/
double mlpeavgce(mlpensemble* ensemble,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_state *_state)
{
    double relcls;
    double avgce;
    double rms;
    double avg;
    double avgrel;
    double result;


    mlpeallerrors(ensemble, xy, npoints, &relcls, &avgce, &rms, &avg, &avgrel, _state);
    result = avgce;
    return result;
}


/*************************************************************************
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.

  -- ALGLIB --
     Copyright 17.02.2009 by Bochkanov Sergey
*************************************************************************/
double mlpermserror(mlpensemble* ensemble,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_state *_state)
{
    double relcls;
    double avgce;
    double rms;
    double avg;
    double avgrel;
    double result;


    mlpeallerrors(ensemble, xy, npoints, &relcls, &avgce, &rms, &avg, &avgrel, _state);
    result = rms;
    return result;
}


/*************************************************************************
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.

  -- ALGLIB --
     Copyright 17.02.2009 by Bochkanov Sergey
*************************************************************************/
double mlpeavgerror(mlpensemble* ensemble,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_state *_state)
{
    double relcls;
    double avgce;
    double rms;
    double avg;
    double avgrel;
    double result;


    mlpeallerrors(ensemble, xy, npoints, &relcls, &avgce, &rms, &avg, &avgrel, _state);
    result = avg;
    return result;
}


/*************************************************************************
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.

  -- ALGLIB --
     Copyright 17.02.2009 by Bochkanov Sergey
*************************************************************************/
double mlpeavgrelerror(mlpensemble* ensemble,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_state *_state)
{
    double relcls;
    double avgce;
    double rms;
    double avg;
    double avgrel;
    double result;


    mlpeallerrors(ensemble, xy, npoints, &relcls, &avgce, &rms, &avg, &avgrel, _state);
    result = avgrel;
    return result;
}


/*************************************************************************
Serializer: allocation

  -- ALGLIB --
     Copyright 19.10.2011 by Bochkanov Sergey
*************************************************************************/
void mlpealloc(ae_serializer* s, mlpensemble* ensemble, ae_state *_state)
{


    ae_serializer_alloc_entry(s);
    ae_serializer_alloc_entry(s);
    ae_serializer_alloc_entry(s);
    allocrealarray(s, &ensemble->weights, -1, _state);
    allocrealarray(s, &ensemble->columnmeans, -1, _state);
    allocrealarray(s, &ensemble->columnsigmas, -1, _state);
    mlpalloc(s, &ensemble->network, _state);
}


/*************************************************************************
Serializer: serialization

  -- ALGLIB --
     Copyright 14.03.2011 by Bochkanov Sergey
*************************************************************************/
void mlpeserialize(ae_serializer* s,
     mlpensemble* ensemble,
     ae_state *_state)
{


    ae_serializer_serialize_int(s, getmlpeserializationcode(_state), _state);
    ae_serializer_serialize_int(s, mlpe_mlpefirstversion, _state);
    ae_serializer_serialize_int(s, ensemble->ensemblesize, _state);
    serializerealarray(s, &ensemble->weights, -1, _state);
    serializerealarray(s, &ensemble->columnmeans, -1, _state);
    serializerealarray(s, &ensemble->columnsigmas, -1, _state);
    mlpserialize(s, &ensemble->network, _state);
}


/*************************************************************************
Serializer: unserialization

  -- ALGLIB --
     Copyright 14.03.2011 by Bochkanov Sergey
*************************************************************************/
void mlpeunserialize(ae_serializer* s,
     mlpensemble* ensemble,
     ae_state *_state)
{
    ae_int_t i0;
    ae_int_t i1;

    _mlpensemble_clear(ensemble);

    
    /*
     * check correctness of header
     */
    ae_serializer_unserialize_int(s, &i0, _state);
    ae_assert(i0==getmlpeserializationcode(_state), "MLPEUnserialize: stream header corrupted", _state);
    ae_serializer_unserialize_int(s, &i1, _state);
    ae_assert(i1==mlpe_mlpefirstversion, "MLPEUnserialize: stream header corrupted", _state);
    
    /*
     * Create network
     */
    ae_serializer_unserialize_int(s, &ensemble->ensemblesize, _state);
    unserializerealarray(s, &ensemble->weights, _state);
    unserializerealarray(s, &ensemble->columnmeans, _state);
    unserializerealarray(s, &ensemble->columnsigmas, _state);
    mlpunserialize(s, &ensemble->network, _state);
    
    /*
     * Allocate termoraries
     */
    ae_vector_set_length(&ensemble->y, mlpgetoutputscount(&ensemble->network, _state), _state);
}


ae_bool _mlpensemble_init(void* _p, ae_state *_state, ae_bool make_automatic)
{
    mlpensemble *p = (mlpensemble*)_p;
    ae_touch_ptr((void*)p);
    if( !ae_vector_init(&p->weights, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->columnmeans, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->columnsigmas, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !_multilayerperceptron_init(&p->network, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->y, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    return ae_true;
}


ae_bool _mlpensemble_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic)
{
    mlpensemble *dst = (mlpensemble*)_dst;
    mlpensemble *src = (mlpensemble*)_src;
    dst->ensemblesize = src->ensemblesize;
    if( !ae_vector_init_copy(&dst->weights, &src->weights, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->columnmeans, &src->columnmeans, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->columnsigmas, &src->columnsigmas, _state, make_automatic) )
        return ae_false;
    if( !_multilayerperceptron_init_copy(&dst->network, &src->network, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->y, &src->y, _state, make_automatic) )
        return ae_false;
    return ae_true;
}


void _mlpensemble_clear(void* _p)
{
    mlpensemble *p = (mlpensemble*)_p;
    ae_touch_ptr((void*)p);
    ae_vector_clear(&p->weights);
    ae_vector_clear(&p->columnmeans);
    ae_vector_clear(&p->columnsigmas);
    _multilayerperceptron_clear(&p->network);
    ae_vector_clear(&p->y);
}


void _mlpensemble_destroy(void* _p)
{
    mlpensemble *p = (mlpensemble*)_p;
    ae_touch_ptr((void*)p);
    ae_vector_destroy(&p->weights);
    ae_vector_destroy(&p->columnmeans);
    ae_vector_destroy(&p->columnsigmas);
    _multilayerperceptron_destroy(&p->network);
    ae_vector_destroy(&p->y);
}




/*************************************************************************
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

  -- ALGLIB --
     Copyright 10.03.2009 by Bochkanov Sergey
*************************************************************************/
void mlptrainlm(multilayerperceptron* network,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     double decay,
     ae_int_t restarts,
     ae_int_t* info,
     mlpreport* rep,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    double lmftol;
    double lmsteptol;
    ae_int_t i;
    ae_int_t k;
    double v;
    double e;
    double enew;
    double xnorm2;
    double stepnorm;
    ae_vector g;
    ae_vector d;
    ae_matrix h;
    ae_matrix hmod;
    ae_matrix z;
    ae_bool spd;
    double nu;
    double lambdav;
    double lambdaup;
    double lambdadown;
    minlbfgsreport internalrep;
    minlbfgsstate state;
    ae_vector x;
    ae_vector y;
    ae_vector wbase;
    ae_vector wdir;
    ae_vector wt;
    ae_vector wx;
    ae_int_t pass;
    ae_vector wbest;
    double ebest;
    ae_int_t invinfo;
    matinvreport invrep;
    ae_int_t solverinfo;
    densesolverreport solverrep;

    ae_frame_make(_state, &_frame_block);
    *info = 0;
    _mlpreport_clear(rep);
    ae_vector_init(&g, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&d, 0, DT_REAL, _state, ae_true);
    ae_matrix_init(&h, 0, 0, DT_REAL, _state, ae_true);
    ae_matrix_init(&hmod, 0, 0, DT_REAL, _state, ae_true);
    ae_matrix_init(&z, 0, 0, DT_REAL, _state, ae_true);
    _minlbfgsreport_init(&internalrep, _state, ae_true);
    _minlbfgsstate_init(&state, _state, ae_true);
    ae_vector_init(&x, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&y, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&wbase, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&wdir, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&wt, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&wx, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&wbest, 0, DT_REAL, _state, ae_true);
    _matinvreport_init(&invrep, _state, ae_true);
    _densesolverreport_init(&solverrep, _state, ae_true);

    mlpproperties(network, &nin, &nout, &wcount, _state);
    lambdaup = 10;
    lambdadown = 0.3;
    lmftol = 0.001;
    lmsteptol = 0.001;
    
    /*
     * Test for inputs
     */
    if( npoints<=0||restarts<1 )
    {
        *info = -1;
        ae_frame_leave(_state);
        return;
    }
    if( mlpissoftmax(network, _state) )
    {
        for(i=0; i<=npoints-1; i++)
        {
            if( ae_round(xy->ptr.pp_double[i][nin], _state)<0||ae_round(xy->ptr.pp_double[i][nin], _state)>=nout )
            {
                *info = -2;
                ae_frame_leave(_state);
                return;
            }
        }
    }
    decay = ae_maxreal(decay, mlptrain_mindecay, _state);
    *info = 2;
    
    /*
     * Initialize data
     */
    rep->ngrad = 0;
    rep->nhess = 0;
    rep->ncholesky = 0;
    
    /*
     * General case.
     * Prepare task and network. Allocate space.
     */
    mlpinitpreprocessor(network, xy, npoints, _state);
    ae_vector_set_length(&g, wcount-1+1, _state);
    ae_matrix_set_length(&h, wcount-1+1, wcount-1+1, _state);
    ae_matrix_set_length(&hmod, wcount-1+1, wcount-1+1, _state);
    ae_vector_set_length(&wbase, wcount-1+1, _state);
    ae_vector_set_length(&wdir, wcount-1+1, _state);
    ae_vector_set_length(&wbest, wcount-1+1, _state);
    ae_vector_set_length(&wt, wcount-1+1, _state);
    ae_vector_set_length(&wx, wcount-1+1, _state);
    ebest = ae_maxrealnumber;
    
    /*
     * Multiple passes
     */
    for(pass=1; pass<=restarts; pass++)
    {
        
        /*
         * Initialize weights
         */
        mlprandomize(network, _state);
        
        /*
         * First stage of the hybrid algorithm: LBFGS
         */
        ae_v_move(&wbase.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
        minlbfgscreate(wcount, ae_minint(wcount, 5, _state), &wbase, &state, _state);
        minlbfgssetcond(&state, 0, 0, 0, ae_maxint(25, wcount, _state), _state);
        while(minlbfgsiteration(&state, _state))
        {
            
            /*
             * gradient
             */
            ae_v_move(&network->weights.ptr.p_double[0], 1, &state.x.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
            mlpgradbatch(network, xy, npoints, &state.f, &state.g, _state);
            
            /*
             * weight decay
             */
            v = ae_v_dotproduct(&network->weights.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
            state.f = state.f+0.5*decay*v;
            ae_v_addd(&state.g.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1), decay);
            
            /*
             * next iteration
             */
            rep->ngrad = rep->ngrad+1;
        }
        minlbfgsresults(&state, &wbase, &internalrep, _state);
        ae_v_move(&network->weights.ptr.p_double[0], 1, &wbase.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
        
        /*
         * Second stage of the hybrid algorithm: LM
         *
         * Initialize H with identity matrix,
         * G with gradient,
         * E with regularized error.
         */
        mlphessianbatch(network, xy, npoints, &e, &g, &h, _state);
        v = ae_v_dotproduct(&network->weights.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
        e = e+0.5*decay*v;
        ae_v_addd(&g.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1), decay);
        for(k=0; k<=wcount-1; k++)
        {
            h.ptr.pp_double[k][k] = h.ptr.pp_double[k][k]+decay;
        }
        rep->nhess = rep->nhess+1;
        lambdav = 0.001;
        nu = 2;
        for(;;)
        {
            
            /*
             * 1. HMod = H+lambda*I
             * 2. Try to solve (H+Lambda*I)*dx = -g.
             *    Increase lambda if left part is not positive definite.
             */
            for(i=0; i<=wcount-1; i++)
            {
                ae_v_move(&hmod.ptr.pp_double[i][0], 1, &h.ptr.pp_double[i][0], 1, ae_v_len(0,wcount-1));
                hmod.ptr.pp_double[i][i] = hmod.ptr.pp_double[i][i]+lambdav;
            }
            spd = spdmatrixcholesky(&hmod, wcount, ae_true, _state);
            rep->ncholesky = rep->ncholesky+1;
            if( !spd )
            {
                lambdav = lambdav*lambdaup*nu;
                nu = nu*2;
                continue;
            }
            spdmatrixcholeskysolve(&hmod, wcount, ae_true, &g, &solverinfo, &solverrep, &wdir, _state);
            if( solverinfo<0 )
            {
                lambdav = lambdav*lambdaup*nu;
                nu = nu*2;
                continue;
            }
            ae_v_muld(&wdir.ptr.p_double[0], 1, ae_v_len(0,wcount-1), -1);
            
            /*
             * Lambda found.
             * 1. Save old w in WBase
             * 1. Test some stopping criterions
             * 2. If error(w+wdir)>error(w), increase lambda
             */
            ae_v_add(&network->weights.ptr.p_double[0], 1, &wdir.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
            xnorm2 = ae_v_dotproduct(&network->weights.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
            stepnorm = ae_v_dotproduct(&wdir.ptr.p_double[0], 1, &wdir.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
            stepnorm = ae_sqrt(stepnorm, _state);
            enew = mlperror(network, xy, npoints, _state)+0.5*decay*xnorm2;
            if( ae_fp_less(stepnorm,lmsteptol*(1+ae_sqrt(xnorm2, _state))) )
            {
                break;
            }
            if( ae_fp_greater(enew,e) )
            {
                lambdav = lambdav*lambdaup*nu;
                nu = nu*2;
                continue;
            }
            
            /*
             * Optimize using inv(cholesky(H)) as preconditioner
             */
            rmatrixtrinverse(&hmod, wcount, ae_true, ae_false, &invinfo, &invrep, _state);
            if( invinfo<=0 )
            {
                
                /*
                 * if matrix can't be inverted then exit with errors
                 * TODO: make WCount steps in direction suggested by HMod
                 */
                *info = -9;
                ae_frame_leave(_state);
                return;
            }
            ae_v_move(&wbase.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
            for(i=0; i<=wcount-1; i++)
            {
                wt.ptr.p_double[i] = 0;
            }
            minlbfgscreatex(wcount, wcount, &wt, 1, 0.0, &state, _state);
            minlbfgssetcond(&state, 0, 0, 0, 5, _state);
            while(minlbfgsiteration(&state, _state))
            {
                
                /*
                 * gradient
                 */
                for(i=0; i<=wcount-1; i++)
                {
                    v = ae_v_dotproduct(&state.x.ptr.p_double[i], 1, &hmod.ptr.pp_double[i][i], 1, ae_v_len(i,wcount-1));
                    network->weights.ptr.p_double[i] = wbase.ptr.p_double[i]+v;
                }
                mlpgradbatch(network, xy, npoints, &state.f, &g, _state);
                for(i=0; i<=wcount-1; i++)
                {
                    state.g.ptr.p_double[i] = 0;
                }
                for(i=0; i<=wcount-1; i++)
                {
                    v = g.ptr.p_double[i];
                    ae_v_addd(&state.g.ptr.p_double[i], 1, &hmod.ptr.pp_double[i][i], 1, ae_v_len(i,wcount-1), v);
                }
                
                /*
                 * weight decay
                 * grad(x'*x) = A'*(x0+A*t)
                 */
                v = ae_v_dotproduct(&network->weights.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
                state.f = state.f+0.5*decay*v;
                for(i=0; i<=wcount-1; i++)
                {
                    v = decay*network->weights.ptr.p_double[i];
                    ae_v_addd(&state.g.ptr.p_double[i], 1, &hmod.ptr.pp_double[i][i], 1, ae_v_len(i,wcount-1), v);
                }
                
                /*
                 * next iteration
                 */
                rep->ngrad = rep->ngrad+1;
            }
            minlbfgsresults(&state, &wt, &internalrep, _state);
            
            /*
             * Accept new position.
             * Calculate Hessian
             */
            for(i=0; i<=wcount-1; i++)
            {
                v = ae_v_dotproduct(&wt.ptr.p_double[i], 1, &hmod.ptr.pp_double[i][i], 1, ae_v_len(i,wcount-1));
                network->weights.ptr.p_double[i] = wbase.ptr.p_double[i]+v;
            }
            mlphessianbatch(network, xy, npoints, &e, &g, &h, _state);
            v = ae_v_dotproduct(&network->weights.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
            e = e+0.5*decay*v;
            ae_v_addd(&g.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1), decay);
            for(k=0; k<=wcount-1; k++)
            {
                h.ptr.pp_double[k][k] = h.ptr.pp_double[k][k]+decay;
            }
            rep->nhess = rep->nhess+1;
            
            /*
             * Update lambda
             */
            lambdav = lambdav*lambdadown;
            nu = 2;
        }
        
        /*
         * update WBest
         */
        v = ae_v_dotproduct(&network->weights.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
        e = 0.5*decay*v+mlperror(network, xy, npoints, _state);
        if( ae_fp_less(e,ebest) )
        {
            ebest = e;
            ae_v_move(&wbest.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
        }
    }
    
    /*
     * copy WBest to output
     */
    ae_v_move(&network->weights.ptr.p_double[0], 1, &wbest.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
    ae_frame_leave(_state);
}


/*************************************************************************
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

  -- ALGLIB --
     Copyright 09.12.2007 by Bochkanov Sergey
*************************************************************************/
void mlptrainlbfgs(multilayerperceptron* network,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     double decay,
     ae_int_t restarts,
     double wstep,
     ae_int_t maxits,
     ae_int_t* info,
     mlpreport* rep,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t i;
    ae_int_t pass;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    ae_vector w;
    ae_vector wbest;
    double e;
    double v;
    double ebest;
    minlbfgsreport internalrep;
    minlbfgsstate state;

    ae_frame_make(_state, &_frame_block);
    *info = 0;
    _mlpreport_clear(rep);
    ae_vector_init(&w, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&wbest, 0, DT_REAL, _state, ae_true);
    _minlbfgsreport_init(&internalrep, _state, ae_true);
    _minlbfgsstate_init(&state, _state, ae_true);

    
    /*
     * Test inputs, parse flags, read network geometry
     */
    if( ae_fp_eq(wstep,0)&&maxits==0 )
    {
        *info = -8;
        ae_frame_leave(_state);
        return;
    }
    if( ((npoints<=0||restarts<1)||ae_fp_less(wstep,0))||maxits<0 )
    {
        *info = -1;
        ae_frame_leave(_state);
        return;
    }
    mlpproperties(network, &nin, &nout, &wcount, _state);
    if( mlpissoftmax(network, _state) )
    {
        for(i=0; i<=npoints-1; i++)
        {
            if( ae_round(xy->ptr.pp_double[i][nin], _state)<0||ae_round(xy->ptr.pp_double[i][nin], _state)>=nout )
            {
                *info = -2;
                ae_frame_leave(_state);
                return;
            }
        }
    }
    decay = ae_maxreal(decay, mlptrain_mindecay, _state);
    *info = 2;
    
    /*
     * Prepare
     */
    mlpinitpreprocessor(network, xy, npoints, _state);
    ae_vector_set_length(&w, wcount-1+1, _state);
    ae_vector_set_length(&wbest, wcount-1+1, _state);
    ebest = ae_maxrealnumber;
    
    /*
     * Multiple starts
     */
    rep->ncholesky = 0;
    rep->nhess = 0;
    rep->ngrad = 0;
    for(pass=1; pass<=restarts; pass++)
    {
        
        /*
         * Process
         */
        mlprandomize(network, _state);
        ae_v_move(&w.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
        minlbfgscreate(wcount, ae_minint(wcount, 10, _state), &w, &state, _state);
        minlbfgssetcond(&state, 0.0, 0.0, wstep, maxits, _state);
        while(minlbfgsiteration(&state, _state))
        {
            ae_v_move(&network->weights.ptr.p_double[0], 1, &state.x.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
            mlpgradnbatch(network, xy, npoints, &state.f, &state.g, _state);
            v = ae_v_dotproduct(&network->weights.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
            state.f = state.f+0.5*decay*v;
            ae_v_addd(&state.g.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1), decay);
            rep->ngrad = rep->ngrad+1;
        }
        minlbfgsresults(&state, &w, &internalrep, _state);
        ae_v_move(&network->weights.ptr.p_double[0], 1, &w.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
        
        /*
         * Compare with best
         */
        v = ae_v_dotproduct(&network->weights.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
        e = mlperrorn(network, xy, npoints, _state)+0.5*decay*v;
        if( ae_fp_less(e,ebest) )
        {
            ae_v_move(&wbest.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
            ebest = e;
        }
    }
    
    /*
     * The best network
     */
    ae_v_move(&network->weights.ptr.p_double[0], 1, &wbest.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
    ae_frame_leave(_state);
}


/*************************************************************************
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, TrnSize>0
    ValXY       -   validation set
    ValSize     -   validation set size, ValSize>0
    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, either:
                    * strictly positive number - algorithm make specified
                      number of restarts from random position.
                    * -1, in which case algorithm makes exactly one run
                      from the initial state of the network (no randomization).
                    If you don't know what Restarts to choose, choose one
                    one the following:
                    * -1 (deterministic start)
                    * +1 (one random restart)
                    * +5 (moderate amount of random restarts)

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.

  -- ALGLIB --
     Copyright 10.03.2009 by Bochkanov Sergey
*************************************************************************/
void mlptraines(multilayerperceptron* network,
     /* Real    */ ae_matrix* trnxy,
     ae_int_t trnsize,
     /* Real    */ ae_matrix* valxy,
     ae_int_t valsize,
     double decay,
     ae_int_t restarts,
     ae_int_t* info,
     mlpreport* rep,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t i;
    ae_int_t pass;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    ae_vector w;
    ae_vector wbest;
    double e;
    double v;
    double ebest;
    ae_vector wfinal;
    double efinal;
    ae_int_t itcnt;
    ae_int_t itbest;
    minlbfgsreport internalrep;
    minlbfgsstate state;
    double wstep;
    ae_bool needrandomization;

    ae_frame_make(_state, &_frame_block);
    *info = 0;
    _mlpreport_clear(rep);
    ae_vector_init(&w, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&wbest, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&wfinal, 0, DT_REAL, _state, ae_true);
    _minlbfgsreport_init(&internalrep, _state, ae_true);
    _minlbfgsstate_init(&state, _state, ae_true);

    wstep = 0.001;
    
    /*
     * Test inputs, parse flags, read network geometry
     */
    if( ((trnsize<=0||valsize<=0)||(restarts<1&&restarts!=-1))||ae_fp_less(decay,0) )
    {
        *info = -1;
        ae_frame_leave(_state);
        return;
    }
    if( restarts==-1 )
    {
        needrandomization = ae_false;
        restarts = 1;
    }
    else
    {
        needrandomization = ae_true;
    }
    mlpproperties(network, &nin, &nout, &wcount, _state);
    if( mlpissoftmax(network, _state) )
    {
        for(i=0; i<=trnsize-1; i++)
        {
            if( ae_round(trnxy->ptr.pp_double[i][nin], _state)<0||ae_round(trnxy->ptr.pp_double[i][nin], _state)>=nout )
            {
                *info = -2;
                ae_frame_leave(_state);
                return;
            }
        }
        for(i=0; i<=valsize-1; i++)
        {
            if( ae_round(valxy->ptr.pp_double[i][nin], _state)<0||ae_round(valxy->ptr.pp_double[i][nin], _state)>=nout )
            {
                *info = -2;
                ae_frame_leave(_state);
                return;
            }
        }
    }
    *info = 2;
    
    /*
     * Prepare
     */
    mlpinitpreprocessor(network, trnxy, trnsize, _state);
    ae_vector_set_length(&w, wcount-1+1, _state);
    ae_vector_set_length(&wbest, wcount-1+1, _state);
    ae_vector_set_length(&wfinal, wcount-1+1, _state);
    efinal = ae_maxrealnumber;
    for(i=0; i<=wcount-1; i++)
    {
        wfinal.ptr.p_double[i] = 0;
    }
    
    /*
     * Multiple starts
     */
    rep->ncholesky = 0;
    rep->nhess = 0;
    rep->ngrad = 0;
    for(pass=1; pass<=restarts; pass++)
    {
        
        /*
         * Process
         */
        if( needrandomization )
        {
            mlprandomize(network, _state);
        }
        ebest = mlperror(network, valxy, valsize, _state);
        ae_v_move(&wbest.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
        itbest = 0;
        itcnt = 0;
        ae_v_move(&w.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
        minlbfgscreate(wcount, ae_minint(wcount, 10, _state), &w, &state, _state);
        minlbfgssetcond(&state, 0.0, 0.0, wstep, 0, _state);
        minlbfgssetxrep(&state, ae_true, _state);
        while(minlbfgsiteration(&state, _state))
        {
            
            /*
             * Calculate gradient
             */
            if( state.needfg )
            {
                ae_v_move(&network->weights.ptr.p_double[0], 1, &state.x.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
                mlpgradnbatch(network, trnxy, trnsize, &state.f, &state.g, _state);
                v = ae_v_dotproduct(&network->weights.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
                state.f = state.f+0.5*decay*v;
                ae_v_addd(&state.g.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1), decay);
                rep->ngrad = rep->ngrad+1;
            }
            
            /*
             * Validation set
             */
            if( state.xupdated )
            {
                ae_v_move(&network->weights.ptr.p_double[0], 1, &state.x.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
                e = mlperror(network, valxy, valsize, _state);
                if( ae_fp_less(e,ebest) )
                {
                    ebest = e;
                    ae_v_move(&wbest.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
                    itbest = itcnt;
                }
                if( itcnt>30&&ae_fp_greater(itcnt,1.5*itbest) )
                {
                    *info = 6;
                    break;
                }
                itcnt = itcnt+1;
            }
        }
        minlbfgsresults(&state, &w, &internalrep, _state);
        
        /*
         * Compare with final answer
         */
        if( ae_fp_less(ebest,efinal) )
        {
            ae_v_move(&wfinal.ptr.p_double[0], 1, &wbest.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
            efinal = ebest;
        }
    }
    
    /*
     * The best network
     */
    ae_v_move(&network->weights.ptr.p_double[0], 1, &wfinal.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
    ae_frame_leave(_state);
}


/*************************************************************************
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

  -- ALGLIB --
     Copyright 09.12.2007 by Bochkanov Sergey
*************************************************************************/
void mlpkfoldcvlbfgs(multilayerperceptron* network,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     double decay,
     ae_int_t restarts,
     double wstep,
     ae_int_t maxits,
     ae_int_t foldscount,
     ae_int_t* info,
     mlpreport* rep,
     mlpcvreport* cvrep,
     ae_state *_state)
{

    *info = 0;
    _mlpreport_clear(rep);
    _mlpcvreport_clear(cvrep);

    mlptrain_mlpkfoldcvgeneral(network, xy, npoints, decay, restarts, foldscount, ae_false, wstep, maxits, info, rep, cvrep, _state);
}


/*************************************************************************
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

  -- ALGLIB --
     Copyright 09.12.2007 by Bochkanov Sergey
*************************************************************************/
void mlpkfoldcvlm(multilayerperceptron* network,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     double decay,
     ae_int_t restarts,
     ae_int_t foldscount,
     ae_int_t* info,
     mlpreport* rep,
     mlpcvreport* cvrep,
     ae_state *_state)
{

    *info = 0;
    _mlpreport_clear(rep);
    _mlpcvreport_clear(cvrep);

    mlptrain_mlpkfoldcvgeneral(network, xy, npoints, decay, restarts, foldscount, ae_true, 0.0, 0, info, rep, cvrep, _state);
}


/*************************************************************************
This function estimates generalization error using cross-validation on the
current dataset with current training settings.


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. Network is not changed  during  cross-
                    validation and is not trained - it  is  used  only  as
                    representative of its architecture. I.e., we  estimate
                    generalization properties of  ARCHITECTURE,  not  some
                    specific network.
    NRestarts   -   number of restarts, >=0:
                    * NRestarts>0  means  that  for  each cross-validation
                      round   specified  number   of  random  restarts  is
                      performed,  with  best  network  being  chosen after
                      training.
                    * NRestarts=0 is same as NRestarts=1
    FoldsCount  -   number of folds in k-fold cross-validation:
                    * 2<=FoldsCount<=size of dataset
                    * recommended value: 10.
                    * values larger than dataset size will be silently
                      truncated down to dataset size

OUTPUT PARAMETERS:
    Rep         -   structure which contains cross-validation estimates:
                    * Rep.RelCLSError - fraction of misclassified cases.
                    * Rep.AvgCE - acerage cross-entropy
                    * Rep.RMSError - root-mean-square error
                    * Rep.AvgError - average error
                    * Rep.AvgRelError - average relative error
                    
NOTE: when no dataset was specified with MLPSetDataset/SetSparseDataset(),
      or subset with only one point  was  given,  zeros  are  returned  as
      estimates.

NOTE: this method performs FoldsCount cross-validation  rounds,  each  one
      with NRestarts random starts.  Thus,  FoldsCount*NRestarts  networks
      are trained in total.

NOTE: Rep.RelCLSError/Rep.AvgCE are zero on regression problems.

NOTE: on classification problems Rep.RMSError/Rep.AvgError/Rep.AvgRelError
      contain errors in prediction of posterior probabilities.
        
  -- ALGLIB --
     Copyright 23.07.2012 by Bochkanov Sergey
*************************************************************************/
void mlpkfoldcv(mlptrainer* s,
     multilayerperceptron* network,
     ae_int_t nrestarts,
     ae_int_t foldscount,
     mlpreport* rep,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_shared_pool pooldatacv;
    mlpparallelizationcv datacv;
    mlpparallelizationcv *sdatacv;
    ae_smart_ptr _sdatacv;
    ae_matrix cvy;
    ae_vector folds;
    ae_vector buf;
    ae_vector dy;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    ae_int_t rowsize;
    ae_int_t ntype;
    ae_int_t ttype;
    ae_int_t i;
    ae_int_t j;
    ae_int_t k;

    ae_frame_make(_state, &_frame_block);
    _mlpreport_clear(rep);
    ae_shared_pool_init(&pooldatacv, _state, ae_true);
    _mlpparallelizationcv_init(&datacv, _state, ae_true);
    ae_smart_ptr_init(&_sdatacv, (void**)&sdatacv, _state, ae_true);
    ae_matrix_init(&cvy, 0, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&folds, 0, DT_INT, _state, ae_true);
    ae_vector_init(&buf, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&dy, 0, DT_REAL, _state, ae_true);

    if( !mlpissoftmax(network, _state) )
    {
        ntype = 0;
    }
    else
    {
        ntype = 1;
    }
    if( s->rcpar )
    {
        ttype = 0;
    }
    else
    {
        ttype = 1;
    }
    ae_assert(ntype==ttype, "MLPKFoldCV: type of input network is not similar to network type in trainer object", _state);
    ae_assert(s->npoints>=0, "MLPKFoldCV: possible trainer S is not initialized(S.NPoints<0)", _state);
    mlpproperties(network, &nin, &nout, &wcount, _state);
    ae_assert(s->nin==nin, "MLPKFoldCV:  number of inputs in trainer is not equal to number of inputs in network", _state);
    ae_assert(s->nout==nout, "MLPKFoldCV:  number of outputs in trainer is not equal to number of outputs in network", _state);
    ae_assert(nrestarts>=0, "MLPKFoldCV: NRestarts<0", _state);
    ae_assert(foldscount>=2, "MLPKFoldCV: FoldsCount<2", _state);
    if( foldscount>s->npoints )
    {
        foldscount = s->npoints;
    }
    rep->relclserror = 0;
    rep->avgce = 0;
    rep->rmserror = 0;
    rep->avgerror = 0;
    rep->avgrelerror = 0;
    rep->ngrad = 0;
    rep->nhess = 0;
    rep->ncholesky = 0;
    if( s->npoints==0||s->npoints==1 )
    {
        ae_frame_leave(_state);
        return;
    }
    
    /*
     * Read network geometry, test parameters
     */
    if( s->rcpar )
    {
        rowsize = nin+nout;
        ae_vector_set_length(&dy, nout, _state);
        dserrallocate(-nout, &buf, _state);
    }
    else
    {
        rowsize = nin+1;
        ae_vector_set_length(&dy, 1, _state);
        dserrallocate(nout, &buf, _state);
    }
    
    /*
     * Folds
     */
    ae_vector_set_length(&folds, s->npoints, _state);
    for(i=0; i<=s->npoints-1; i++)
    {
        folds.ptr.p_int[i] = i*foldscount/s->npoints;
    }
    for(i=0; i<=s->npoints-2; i++)
    {
        j = i+ae_randominteger(s->npoints-i, _state);
        if( j!=i )
        {
            k = folds.ptr.p_int[i];
            folds.ptr.p_int[i] = folds.ptr.p_int[j];
            folds.ptr.p_int[j] = k;
        }
    }
    ae_matrix_set_length(&cvy, s->npoints, nout, _state);
    
    /*
     * Initialize SEED-value for shared pool
     */
    datacv.ngrad = 0;
    mlpcopy(network, &datacv.network, _state);
    mlpcopy(network, &datacv.tnetwork, _state);
    ae_vector_set_length(&datacv.subset, s->npoints, _state);
    ae_vector_set_length(&datacv.xyrow, rowsize, _state);
    ae_vector_set_length(&datacv.bufwbest, wcount, _state);
    ae_vector_set_length(&datacv.bufwfinal, wcount, _state);
    ae_vector_set_length(&datacv.y, nout, _state);
    
    /*
     * Initialize LBFGS optimizer
     */
    minlbfgscreate(wcount, ae_minint(wcount, s->lbfgsfactor, _state), &network->weights, &datacv.state, _state);
    minlbfgssetcond(&datacv.state, 0.0, 0.0, s->wstep, s->maxits, _state);
    minlbfgssetxrep(&datacv.state, ae_true, _state);
    
    /*
     * Create shared pool
     */
    ae_shared_pool_set_seed(&pooldatacv, &datacv, sizeof(datacv), _mlpparallelizationcv_init, _mlpparallelizationcv_init_copy, _mlpparallelizationcv_destroy, _state);
    
    /*
     * Parallelization
     */
    mlptrain_mthreadcv(s, rowsize, nrestarts, &folds, 0, foldscount, &cvy, &pooldatacv, _state);
    
    /*
     * Calculate value for NGrad
     */
    ae_shared_pool_first_recycled(&pooldatacv, &_sdatacv, _state);
    while(sdatacv!=NULL)
    {
        rep->ngrad = rep->ngrad+sdatacv->ngrad;
        ae_shared_pool_next_recycled(&pooldatacv, &_sdatacv, _state);
    }
    
    /*
     * Connect of results and calculate cross-validation error
     */
    for(i=0; i<=s->npoints-1; i++)
    {
        if( s->datatype==0 )
        {
            ae_v_move(&datacv.xyrow.ptr.p_double[0], 1, &s->densexy.ptr.pp_double[i][0], 1, ae_v_len(0,rowsize-1));
        }
        if( s->datatype==1 )
        {
            sparsegetrow(&s->sparsexy, i, &datacv.xyrow, _state);
        }
        ae_v_move(&datacv.y.ptr.p_double[0], 1, &cvy.ptr.pp_double[i][0], 1, ae_v_len(0,nout-1));
        if( s->rcpar )
        {
            ae_v_move(&dy.ptr.p_double[0], 1, &datacv.xyrow.ptr.p_double[nin], 1, ae_v_len(0,nout-1));
        }
        else
        {
            dy.ptr.p_double[0] = datacv.xyrow.ptr.p_double[nin];
        }
        dserraccumulate(&buf, &datacv.y, &dy, _state);
    }
    dserrfinish(&buf, _state);
    rep->relclserror = buf.ptr.p_double[0];
    rep->avgce = buf.ptr.p_double[1];
    rep->rmserror = buf.ptr.p_double[2];
    rep->avgerror = buf.ptr.p_double[3];
    rep->avgrelerror = buf.ptr.p_double[4];
    ae_frame_leave(_state);
}


/*************************************************************************
Single-threaded stub. HPC ALGLIB replaces it by multithreaded code.
*************************************************************************/
void _pexec_mlpkfoldcv(mlptrainer* s,
    multilayerperceptron* network,
    ae_int_t nrestarts,
    ae_int_t foldscount,
    mlpreport* rep, ae_state *_state)
{
    mlpkfoldcv(s,network,nrestarts,foldscount,rep, _state);
}


/*************************************************************************
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.

  -- ALGLIB --
     Copyright 23.07.2012 by Bochkanov Sergey
*************************************************************************/
void mlpcreatetrainer(ae_int_t nin,
     ae_int_t nout,
     mlptrainer* s,
     ae_state *_state)
{

    _mlptrainer_clear(s);

    ae_assert(nin>=1, "MLPCreateTrainer: NIn<1.", _state);
    ae_assert(nout>=1, "MLPCreateTrainer: NOut<1.", _state);
    s->nin = nin;
    s->nout = nout;
    s->rcpar = ae_true;
    s->lbfgsfactor = 10;
    s->decay = 1.0E-6;
    mlpsetcond(s, 0, 0, _state);
    s->datatype = 0;
    s->npoints = 0;
}


/*************************************************************************
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.

  -- ALGLIB --
     Copyright 23.07.2012 by Bochkanov Sergey
*************************************************************************/
void mlpcreatetrainercls(ae_int_t nin,
     ae_int_t nclasses,
     mlptrainer* s,
     ae_state *_state)
{

    _mlptrainer_clear(s);

    ae_assert(nin>=1, "MLPCreateTrainerCls: NIn<1.", _state);
    ae_assert(nclasses>=2, "MLPCreateTrainerCls: NClasses<2.", _state);
    s->nin = nin;
    s->nout = nclasses;
    s->rcpar = ae_false;
    s->lbfgsfactor = 10;
    s->decay = 1.0E-6;
    mlpsetcond(s, 0, 0, _state);
    s->datatype = 0;
    s->npoints = 0;
}


/*************************************************************************
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).
  
  -- ALGLIB --
     Copyright 23.07.2012 by Bochkanov Sergey
*************************************************************************/
void mlpsetdataset(mlptrainer* s,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_state *_state)
{
    ae_int_t ndim;
    ae_int_t i;
    ae_int_t j;


    ae_assert(s->nin>=1, "MLPSetDataset: possible parameter S is not initialized or spoiled(S.NIn<=0).", _state);
    ae_assert(npoints>=0, "MLPSetDataset: NPoint<0", _state);
    ae_assert(npoints<=xy->rows, "MLPSetDataset: invalid size of matrix XY(NPoint more then rows of matrix XY)", _state);
    s->datatype = 0;
    s->npoints = npoints;
    if( npoints==0 )
    {
        return;
    }
    if( s->rcpar )
    {
        ae_assert(s->nout>=1, "MLPSetDataset: possible parameter S is not initialized or is spoiled(NOut<1 for regression).", _state);
        ndim = s->nin+s->nout;
        ae_assert(ndim<=xy->cols, "MLPSetDataset: invalid size of matrix XY(too few columns in matrix XY).", _state);
        ae_assert(apservisfinitematrix(xy, npoints, ndim, _state), "MLPSetDataset: parameter XY contains Infinite or NaN.", _state);
    }
    else
    {
        ae_assert(s->nout>=2, "MLPSetDataset: possible parameter S is not initialized or is spoiled(NClasses<2 for classifier).", _state);
        ndim = s->nin+1;
        ae_assert(ndim<=xy->cols, "MLPSetDataset: invalid size of matrix XY(too few columns in matrix XY).", _state);
        ae_assert(apservisfinitematrix(xy, npoints, ndim, _state), "MLPSetDataset: parameter XY contains Infinite or NaN.", _state);
        for(i=0; i<=npoints-1; i++)
        {
            ae_assert(ae_round(xy->ptr.pp_double[i][s->nin], _state)>=0&&ae_round(xy->ptr.pp_double[i][s->nin], _state)<s->nout, "MLPSetDataset: invalid parameter XY(in classifier used nonexistent class number: either XY[.,NIn]<0 or XY[.,NIn]>=NClasses).", _state);
        }
    }
    rmatrixsetlengthatleast(&s->densexy, npoints, ndim, _state);
    for(i=0; i<=npoints-1; i++)
    {
        for(j=0; j<=ndim-1; j++)
        {
            s->densexy.ptr.pp_double[i][j] = xy->ptr.pp_double[i][j];
        }
    }
}


/*************************************************************************
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).
  
  -- ALGLIB --
     Copyright 23.07.2012 by Bochkanov Sergey
*************************************************************************/
void mlpsetsparsedataset(mlptrainer* s,
     sparsematrix* xy,
     ae_int_t npoints,
     ae_state *_state)
{
    double v;
    ae_int_t t0;
    ae_int_t t1;
    ae_int_t i;
    ae_int_t j;


    ae_assert(s->nin>0, "MLPSetSparseDataset: possible parameter S is not initialized or spoiled(S.NIn<=0).", _state);
    ae_assert(npoints>=0, "MLPSetSparseDataset: NPoint<0", _state);
    ae_assert(npoints<=sparsegetnrows(xy, _state), "MLPSetSparseDataset: invalid size of sparse matrix XY(NPoint more then rows of matrix XY)", _state);
    s->datatype = 1;
    s->npoints = npoints;
    if( npoints==0 )
    {
        return;
    }
    t0 = 0;
    t1 = 0;
    if( s->rcpar )
    {
        ae_assert(s->nout>=1, "MLPSetSparseDataset: possible parameter S is not initialized or is spoiled(NOut<1 for regression).", _state);
        ae_assert(s->nin+s->nout<=sparsegetncols(xy, _state), "MLPSetSparseDataset: invalid size of sparse matrix XY(too few columns in sparse matrix XY).", _state);
        while(sparseenumerate(xy, &t0, &t1, &i, &j, &v, _state))
        {
            if( i<npoints&&j<s->nin+s->nout )
            {
                ae_assert(ae_isfinite(v, _state), "MLPSetSparseDataset: sparse matrix XY contains Infinite or NaN.", _state);
            }
        }
    }
    else
    {
        ae_assert(s->nout>=2, "MLPSetSparseDataset: possible parameter S is not initialized or is spoiled(NClasses<2 for classifier).", _state);
        ae_assert(s->nin+1<=sparsegetncols(xy, _state), "MLPSetSparseDataset: invalid size of sparse matrix XY(too few columns in sparse matrix XY).", _state);
        while(sparseenumerate(xy, &t0, &t1, &i, &j, &v, _state))
        {
            if( i<npoints&&j<=s->nin )
            {
                if( j!=s->nin )
                {
                    ae_assert(ae_isfinite(v, _state), "MLPSetSparseDataset: sparse matrix XY contains Infinite or NaN.", _state);
                }
                else
                {
                    ae_assert((ae_isfinite(v, _state)&&ae_round(v, _state)>=0)&&ae_round(v, _state)<s->nout, "MLPSetSparseDataset: invalid sparse matrix XY(in classifier used nonexistent class number: either XY[.,NIn]<0 or XY[.,NIn]>=NClasses).", _state);
                }
            }
        }
    }
    sparsecopytocrs(xy, &s->sparsexy, _state);
}


/*************************************************************************
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.

  -- ALGLIB --
     Copyright 23.07.2012 by Bochkanov Sergey
*************************************************************************/
void mlpsetdecay(mlptrainer* s, double decay, ae_state *_state)
{


    ae_assert(ae_isfinite(decay, _state), "MLPSetDecay: parameter Decay contains Infinite or NaN.", _state);
    ae_assert(ae_fp_greater_eq(decay,0), "MLPSetDecay: Decay<0.", _state);
    s->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
                    iterations (NOT gradient  calculations).  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.

  -- ALGLIB --
     Copyright 23.07.2012 by Bochkanov Sergey
*************************************************************************/
void mlpsetcond(mlptrainer* s,
     double wstep,
     ae_int_t maxits,
     ae_state *_state)
{


    ae_assert(ae_isfinite(wstep, _state), "MLPSetCond: parameter WStep contains Infinite or NaN.", _state);
    ae_assert(ae_fp_greater_eq(wstep,0), "MLPSetCond: WStep<0.", _state);
    ae_assert(maxits>=0, "MLPSetCond: MaxIts<0.", _state);
    if( ae_fp_neq(wstep,0)||maxits!=0 )
    {
        s->wstep = wstep;
        s->maxits = maxits;
    }
    else
    {
        s->wstep = 0.005;
        s->maxits = 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.

  -- ALGLIB --
     Copyright 23.07.2012 by Bochkanov Sergey
*************************************************************************/
void mlptrainnetwork(mlptrainer* s,
     multilayerperceptron* network,
     ae_int_t nrestarts,
     mlpreport* rep,
     ae_state *_state)
{
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    ae_int_t ntype;
    ae_int_t ttype;

    _mlpreport_clear(rep);

    ae_assert(s->npoints>=0, "MLPTrainNetwork: parameter S is not initialized or is spoiled(S.NPoints<0)", _state);
    if( !mlpissoftmax(network, _state) )
    {
        ntype = 0;
    }
    else
    {
        ntype = 1;
    }
    if( s->rcpar )
    {
        ttype = 0;
    }
    else
    {
        ttype = 1;
    }
    ae_assert(ntype==ttype, "MLPTrainNetwork: type of input network is not similar to network type in trainer object", _state);
    mlpproperties(network, &nin, &nout, &wcount, _state);
    ae_assert(s->nin==nin, "MLPTrainNetwork: number of inputs in trainer is not equal to number of inputs in network", _state);
    ae_assert(s->nout==nout, "MLPTrainNetwork: number of outputs in trainer is not equal to number of outputs in network", _state);
    ae_assert(nrestarts>=0, "MLPTrainNetwork: NRestarts<0.", _state);
    rvectorsetlengthatleast(&s->wbest, wcount, _state);
    rvectorsetlengthatleast(&s->wfinal, wcount, _state);
    
    /*
     * Create LBFGS optimizer
     */
    minlbfgscreate(wcount, ae_minint(wcount, s->lbfgsfactor, _state), &network->weights, &s->tstate, _state);
    minlbfgssetcond(&s->tstate, 0.0, 0.0, s->wstep, s->maxits, _state);
    minlbfgssetxrep(&s->tstate, ae_true, _state);
    
    /*
     * Create duplicate of the network
     */
    mlpcopy(network, &s->tnetwork, _state);
    
    /*
     * Train
     */
    mlptrain_mlptrainnetworkx(s, network, &s->tnetwork, &s->tstate, nrestarts, &s->subset, -1, &s->subset, 0, &s->wbest, &s->wfinal, rep, _state);
}


/*************************************************************************
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.

  -- ALGLIB --
     Copyright 23.07.2012 by Bochkanov Sergey
*************************************************************************/
void mlpstarttraining(mlptrainer* s,
     multilayerperceptron* network,
     ae_bool randomstart,
     ae_state *_state)
{
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    ae_int_t ntype;
    ae_int_t ttype;


    ae_assert(s->npoints>=0, "MLPStartTraining: parameter S is not initialized or is spoiled(S.NPoints<0)", _state);
    if( !mlpissoftmax(network, _state) )
    {
        ntype = 0;
    }
    else
    {
        ntype = 1;
    }
    if( s->rcpar )
    {
        ttype = 0;
    }
    else
    {
        ttype = 1;
    }
    ae_assert(ntype==ttype, "MLPStartTraining: type of input network is not similar to network type in trainer object", _state);
    mlpproperties(network, &nin, &nout, &wcount, _state);
    ae_assert(s->nin==nin, "MLPStartTraining: number of inputs in trainer is not equal to number of inputs in the network.", _state);
    ae_assert(s->nout==nout, "MLPStartTraining: number of outputs in trainer is not equal to number of outputs in the network.", _state);
    
    /*
     * Create LBFGS optimizer
     */
    minlbfgscreate(wcount, ae_minint(wcount, s->lbfgsfactor, _state), &network->weights, &s->tstate, _state);
    minlbfgssetcond(&s->tstate, 0.0, 0.0, s->wstep, s->maxits, _state);
    minlbfgssetxrep(&s->tstate, ae_true, _state);
    
    /*
     * Create duplicate of the network
     */
    mlpcopy(network, &s->tnetwork, _state);
    
    /*
     * Train network
     */
    mlptrain_mlpstarttrainingx(s, network, &s->tnetwork, &s->tstate, randomstart, &s->subset, -1, _state);
}


/*************************************************************************
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.
      

  -- ALGLIB --
     Copyright 23.07.2012 by Bochkanov Sergey
*************************************************************************/
ae_bool mlpcontinuetraining(mlptrainer* s,
     multilayerperceptron* network,
     ae_state *_state)
{
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    ae_int_t ntype;
    ae_int_t ttype;
    ae_bool result;


    ae_assert(s->npoints>=0, "MLPContinueTraining: parameter S is not initialized or is spoiled(S.NPoints<0)", _state);
    if( s->rcpar )
    {
        ttype = 0;
    }
    else
    {
        ttype = 1;
    }
    if( !mlpissoftmax(network, _state) )
    {
        ntype = 0;
    }
    else
    {
        ntype = 1;
    }
    ae_assert(ntype==ttype, "MLPContinueTraining: type of input network is not similar to network type in trainer object.", _state);
    mlpproperties(network, &nin, &nout, &wcount, _state);
    ae_assert(s->nin==nin, "MLPContinueTraining: number of inputs in trainer is not equal to number of inputs in the network.", _state);
    ae_assert(s->nout==nout, "MLPContinueTraining: number of outputs in trainer is not equal to number of outputs in the network.", _state);
    result = mlptrain_mlpcontinuetrainingx(s, network, &s->tnetwork, &s->tstate, &s->subset, -1, &s->ngradbatch, _state);
    return result;
}


/*************************************************************************
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

  -- ALGLIB --
     Copyright 17.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpebagginglm(mlpensemble* ensemble,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     double decay,
     ae_int_t restarts,
     ae_int_t* info,
     mlpreport* rep,
     mlpcvreport* ooberrors,
     ae_state *_state)
{

    *info = 0;
    _mlpreport_clear(rep);
    _mlpcvreport_clear(ooberrors);

    mlptrain_mlpebagginginternal(ensemble, xy, npoints, decay, restarts, 0.0, 0, ae_true, info, rep, ooberrors, _state);
}


/*************************************************************************
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

  -- ALGLIB --
     Copyright 17.02.2009 by Bochkanov Sergey
*************************************************************************/
void mlpebagginglbfgs(mlpensemble* ensemble,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     double decay,
     ae_int_t restarts,
     double wstep,
     ae_int_t maxits,
     ae_int_t* info,
     mlpreport* rep,
     mlpcvreport* ooberrors,
     ae_state *_state)
{

    *info = 0;
    _mlpreport_clear(rep);
    _mlpcvreport_clear(ooberrors);

    mlptrain_mlpebagginginternal(ensemble, xy, npoints, decay, restarts, wstep, maxits, ae_false, info, rep, ooberrors, _state);
}


/*************************************************************************
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

  -- ALGLIB --
     Copyright 10.03.2009 by Bochkanov Sergey
*************************************************************************/
void mlpetraines(mlpensemble* ensemble,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     double decay,
     ae_int_t restarts,
     ae_int_t* info,
     mlpreport* rep,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t i;
    ae_int_t k;
    ae_int_t ccount;
    ae_int_t pcount;
    ae_matrix trnxy;
    ae_matrix valxy;
    ae_int_t trnsize;
    ae_int_t valsize;
    ae_int_t tmpinfo;
    mlpreport tmprep;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;

    ae_frame_make(_state, &_frame_block);
    *info = 0;
    _mlpreport_clear(rep);
    ae_matrix_init(&trnxy, 0, 0, DT_REAL, _state, ae_true);
    ae_matrix_init(&valxy, 0, 0, DT_REAL, _state, ae_true);
    _mlpreport_init(&tmprep, _state, ae_true);

    nin = mlpgetinputscount(&ensemble->network, _state);
    nout = mlpgetoutputscount(&ensemble->network, _state);
    wcount = mlpgetweightscount(&ensemble->network, _state);
    if( (npoints<2||restarts<1)||ae_fp_less(decay,0) )
    {
        *info = -1;
        ae_frame_leave(_state);
        return;
    }
    if( mlpissoftmax(&ensemble->network, _state) )
    {
        for(i=0; i<=npoints-1; i++)
        {
            if( ae_round(xy->ptr.pp_double[i][nin], _state)<0||ae_round(xy->ptr.pp_double[i][nin], _state)>=nout )
            {
                *info = -2;
                ae_frame_leave(_state);
                return;
            }
        }
    }
    *info = 6;
    
    /*
     * allocate
     */
    if( mlpissoftmax(&ensemble->network, _state) )
    {
        ccount = nin+1;
        pcount = nin;
    }
    else
    {
        ccount = nin+nout;
        pcount = nin+nout;
    }
    ae_matrix_set_length(&trnxy, npoints, ccount, _state);
    ae_matrix_set_length(&valxy, npoints, ccount, _state);
    rep->ngrad = 0;
    rep->nhess = 0;
    rep->ncholesky = 0;
    
    /*
     * train networks
     */
    for(k=0; k<=ensemble->ensemblesize-1; k++)
    {
        
        /*
         * Split set
         */
        do
        {
            trnsize = 0;
            valsize = 0;
            for(i=0; i<=npoints-1; i++)
            {
                if( ae_fp_less(ae_randomreal(_state),0.66) )
                {
                    
                    /*
                     * Assign sample to training set
                     */
                    ae_v_move(&trnxy.ptr.pp_double[trnsize][0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,ccount-1));
                    trnsize = trnsize+1;
                }
                else
                {
                    
                    /*
                     * Assign sample to validation set
                     */
                    ae_v_move(&valxy.ptr.pp_double[valsize][0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,ccount-1));
                    valsize = valsize+1;
                }
            }
        }
        while(!(trnsize!=0&&valsize!=0));
        
        /*
         * Train
         */
        mlptraines(&ensemble->network, &trnxy, trnsize, &valxy, valsize, decay, restarts, &tmpinfo, &tmprep, _state);
        if( tmpinfo<0 )
        {
            *info = tmpinfo;
            ae_frame_leave(_state);
            return;
        }
        
        /*
         * save results
         */
        ae_v_move(&ensemble->weights.ptr.p_double[k*wcount], 1, &ensemble->network.weights.ptr.p_double[0], 1, ae_v_len(k*wcount,(k+1)*wcount-1));
        ae_v_move(&ensemble->columnmeans.ptr.p_double[k*pcount], 1, &ensemble->network.columnmeans.ptr.p_double[0], 1, ae_v_len(k*pcount,(k+1)*pcount-1));
        ae_v_move(&ensemble->columnsigmas.ptr.p_double[k*pcount], 1, &ensemble->network.columnsigmas.ptr.p_double[0], 1, ae_v_len(k*pcount,(k+1)*pcount-1));
        rep->ngrad = rep->ngrad+tmprep.ngrad;
        rep->nhess = rep->nhess+tmprep.nhess;
        rep->ncholesky = rep->ncholesky+tmprep.ncholesky;
    }
    ae_frame_leave(_state);
}


/*************************************************************************
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: 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.

  -- ALGLIB --
     Copyright 22.08.2012 by Bochkanov Sergey
*************************************************************************/
void mlptrainensemblees(mlptrainer* s,
     mlpensemble* ensemble,
     ae_int_t nrestarts,
     mlpreport* rep,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t pcount;
    mlpreport tmprep;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    ae_int_t ntype;
    ae_int_t ttype;
    ae_int_t i;
    ae_int_t k;

    ae_frame_make(_state, &_frame_block);
    _mlpreport_clear(rep);
    _mlpreport_init(&tmprep, _state, ae_true);

    ae_assert(s->npoints>=0, "MLPTrainEnsembleES: parameter S is not initialized or is spoiled(S.NPoints<0)", _state);
    if( !mlpeissoftmax(ensemble, _state) )
    {
        ntype = 0;
    }
    else
    {
        ntype = 1;
    }
    if( s->rcpar )
    {
        ttype = 0;
    }
    else
    {
        ttype = 1;
    }
    ae_assert(ntype==ttype, "MLPTrainEnsembleES: internal error - type of input network is not similar to network type in trainer object", _state);
    nin = mlpgetinputscount(&ensemble->network, _state);
    ae_assert(s->nin==nin, "MLPTrainEnsembleES: number of inputs in trainer is not equal to number of inputs in ensemble network", _state);
    nout = mlpgetoutputscount(&ensemble->network, _state);
    ae_assert(s->nout==nout, "MLPTrainEnsembleES: number of outputs in trainer is not equal to number of outputs in ensemble network", _state);
    ae_assert(nrestarts>=0, "MLPTrainEnsembleES: NRestarts<0.", _state);
    wcount = mlpgetweightscount(&ensemble->network, _state);
    
    /*
     * Initialize parameter Rep
     */
    rep->relclserror = 0;
    rep->avgce = 0;
    rep->rmserror = 0;
    rep->avgerror = 0;
    rep->avgrelerror = 0;
    rep->ngrad = 0;
    rep->nhess = 0;
    rep->ncholesky = 0;
    
    /*
     * Allocate
     */
    if( mlpissoftmax(&ensemble->network, _state) )
    {
        pcount = nin;
    }
    else
    {
        pcount = nin+nout;
    }
    ivectorsetlengthatleast(&s->subset, s->npoints, _state);
    ivectorsetlengthatleast(&s->valsubset, s->npoints, _state);
    rvectorsetlengthatleast(&s->wbest, wcount, _state);
    rvectorsetlengthatleast(&s->wfinal, wcount, _state);
    
    /*
     * Create LBFGS optimizer
     */
    minlbfgscreate(wcount, ae_minint(wcount, s->lbfgsfactor, _state), &ensemble->network.weights, &s->tstate, _state);
    minlbfgssetcond(&s->tstate, 0.0, 0.0, s->wstep, s->maxits, _state);
    minlbfgssetxrep(&s->tstate, ae_true, _state);
    mlpcopy(&ensemble->network, &s->tnetwork, _state);
    
    /*
     * Train networks
     */
    if( (s->datatype==0||s->datatype==1)&&s->npoints>1 )
    {
        for(k=0; k<=ensemble->ensemblesize-1; k++)
        {
            
            /*
             * Split set
             */
            do
            {
                s->subsetsize = 0;
                s->valsubsetsize = 0;
                for(i=0; i<=s->npoints-1; i++)
                {
                    if( ae_fp_less(ae_randomreal(_state),0.66) )
                    {
                        
                        /*
                         * Assign sample to training set
                         */
                        s->subset.ptr.p_int[s->subsetsize] = i;
                        s->subsetsize = s->subsetsize+1;
                    }
                    else
                    {
                        
                        /*
                         * Assign sample to validation set
                         */
                        s->valsubset.ptr.p_int[s->valsubsetsize] = i;
                        s->valsubsetsize = s->valsubsetsize+1;
                    }
                }
            }
            while(!(s->subsetsize!=0&&s->valsubsetsize!=0));
            
            /*
             * Train
             */
            mlptrain_mlptrainnetworkx(s, &ensemble->network, &s->tnetwork, &s->tstate, nrestarts, &s->subset, s->subsetsize, &s->valsubset, s->valsubsetsize, &s->wbest, &s->wfinal, &tmprep, _state);
            rep->ngrad = rep->ngrad+tmprep.ngrad;
            
            /*
             * Save results
             */
            ae_v_move(&ensemble->weights.ptr.p_double[k*wcount], 1, &ensemble->network.weights.ptr.p_double[0], 1, ae_v_len(k*wcount,(k+1)*wcount-1));
            ae_v_move(&ensemble->columnmeans.ptr.p_double[k*pcount], 1, &ensemble->network.columnmeans.ptr.p_double[0], 1, ae_v_len(k*pcount,(k+1)*pcount-1));
            ae_v_move(&ensemble->columnsigmas.ptr.p_double[k*pcount], 1, &ensemble->network.columnsigmas.ptr.p_double[0], 1, ae_v_len(k*pcount,(k+1)*pcount-1));
        }
    }
    else
    {
        for(i=0; i<=ensemble->ensemblesize*wcount-1; i++)
        {
            ensemble->network.weights.ptr.p_double[i] = 0.0;
            ensemble->columnmeans.ptr.p_double[i] = 0.0;
            ensemble->columnsigmas.ptr.p_double[i] = 1.0;
        }
    }
    
    /*
     * Calculate errors.
     */
    if( s->datatype==0 )
    {
        mlpeallerrors(ensemble, &s->densexy, s->npoints, &rep->relclserror, &rep->avgce, &rep->rmserror, &rep->avgerror, &rep->avgrelerror, _state);
    }
    if( s->datatype==1 )
    {
        mlpeallerrorssparse(ensemble, &s->sparsexy, s->npoints, &rep->relclserror, &rep->avgce, &rep->rmserror, &rep->avgerror, &rep->avgrelerror, _state);
    }
    ae_frame_leave(_state);
}


/*************************************************************************
Internal cross-validation subroutine
*************************************************************************/
static void mlptrain_mlpkfoldcvgeneral(multilayerperceptron* n,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     double decay,
     ae_int_t restarts,
     ae_int_t foldscount,
     ae_bool lmalgorithm,
     double wstep,
     ae_int_t maxits,
     ae_int_t* info,
     mlpreport* rep,
     mlpcvreport* cvrep,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_int_t i;
    ae_int_t fold;
    ae_int_t j;
    ae_int_t k;
    multilayerperceptron network;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t rowlen;
    ae_int_t wcount;
    ae_int_t nclasses;
    ae_int_t tssize;
    ae_int_t cvssize;
    ae_matrix cvset;
    ae_matrix testset;
    ae_vector folds;
    ae_int_t relcnt;
    mlpreport internalrep;
    ae_vector x;
    ae_vector y;

    ae_frame_make(_state, &_frame_block);
    *info = 0;
    _mlpreport_clear(rep);
    _mlpcvreport_clear(cvrep);
    _multilayerperceptron_init(&network, _state, ae_true);
    ae_matrix_init(&cvset, 0, 0, DT_REAL, _state, ae_true);
    ae_matrix_init(&testset, 0, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&folds, 0, DT_INT, _state, ae_true);
    _mlpreport_init(&internalrep, _state, ae_true);
    ae_vector_init(&x, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&y, 0, DT_REAL, _state, ae_true);

    
    /*
     * Read network geometry, test parameters
     */
    mlpproperties(n, &nin, &nout, &wcount, _state);
    if( mlpissoftmax(n, _state) )
    {
        nclasses = nout;
        rowlen = nin+1;
    }
    else
    {
        nclasses = -nout;
        rowlen = nin+nout;
    }
    if( (npoints<=0||foldscount<2)||foldscount>npoints )
    {
        *info = -1;
        ae_frame_leave(_state);
        return;
    }
    mlpcopy(n, &network, _state);
    
    /*
     * K-fold out cross-validation.
     * First, estimate generalization error
     */
    ae_matrix_set_length(&testset, npoints-1+1, rowlen-1+1, _state);
    ae_matrix_set_length(&cvset, npoints-1+1, rowlen-1+1, _state);
    ae_vector_set_length(&x, nin-1+1, _state);
    ae_vector_set_length(&y, nout-1+1, _state);
    mlptrain_mlpkfoldsplit(xy, npoints, nclasses, foldscount, ae_false, &folds, _state);
    cvrep->relclserror = 0;
    cvrep->avgce = 0;
    cvrep->rmserror = 0;
    cvrep->avgerror = 0;
    cvrep->avgrelerror = 0;
    rep->ngrad = 0;
    rep->nhess = 0;
    rep->ncholesky = 0;
    relcnt = 0;
    for(fold=0; fold<=foldscount-1; fold++)
    {
        
        /*
         * Separate set
         */
        tssize = 0;
        cvssize = 0;
        for(i=0; i<=npoints-1; i++)
        {
            if( folds.ptr.p_int[i]==fold )
            {
                ae_v_move(&testset.ptr.pp_double[tssize][0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,rowlen-1));
                tssize = tssize+1;
            }
            else
            {
                ae_v_move(&cvset.ptr.pp_double[cvssize][0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,rowlen-1));
                cvssize = cvssize+1;
            }
        }
        
        /*
         * Train on CV training set
         */
        if( lmalgorithm )
        {
            mlptrainlm(&network, &cvset, cvssize, decay, restarts, info, &internalrep, _state);
        }
        else
        {
            mlptrainlbfgs(&network, &cvset, cvssize, decay, restarts, wstep, maxits, info, &internalrep, _state);
        }
        if( *info<0 )
        {
            cvrep->relclserror = 0;
            cvrep->avgce = 0;
            cvrep->rmserror = 0;
            cvrep->avgerror = 0;
            cvrep->avgrelerror = 0;
            ae_frame_leave(_state);
            return;
        }
        rep->ngrad = rep->ngrad+internalrep.ngrad;
        rep->nhess = rep->nhess+internalrep.nhess;
        rep->ncholesky = rep->ncholesky+internalrep.ncholesky;
        
        /*
         * Estimate error using CV test set
         */
        if( mlpissoftmax(&network, _state) )
        {
            
            /*
             * classification-only code
             */
            cvrep->relclserror = cvrep->relclserror+mlpclserror(&network, &testset, tssize, _state);
            cvrep->avgce = cvrep->avgce+mlperrorn(&network, &testset, tssize, _state);
        }
        for(i=0; i<=tssize-1; i++)
        {
            ae_v_move(&x.ptr.p_double[0], 1, &testset.ptr.pp_double[i][0], 1, ae_v_len(0,nin-1));
            mlpprocess(&network, &x, &y, _state);
            if( mlpissoftmax(&network, _state) )
            {
                
                /*
                 * Classification-specific code
                 */
                k = ae_round(testset.ptr.pp_double[i][nin], _state);
                for(j=0; j<=nout-1; j++)
                {
                    if( j==k )
                    {
                        cvrep->rmserror = cvrep->rmserror+ae_sqr(y.ptr.p_double[j]-1, _state);
                        cvrep->avgerror = cvrep->avgerror+ae_fabs(y.ptr.p_double[j]-1, _state);
                        cvrep->avgrelerror = cvrep->avgrelerror+ae_fabs(y.ptr.p_double[j]-1, _state);
                        relcnt = relcnt+1;
                    }
                    else
                    {
                        cvrep->rmserror = cvrep->rmserror+ae_sqr(y.ptr.p_double[j], _state);
                        cvrep->avgerror = cvrep->avgerror+ae_fabs(y.ptr.p_double[j], _state);
                    }
                }
            }
            else
            {
                
                /*
                 * Regression-specific code
                 */
                for(j=0; j<=nout-1; j++)
                {
                    cvrep->rmserror = cvrep->rmserror+ae_sqr(y.ptr.p_double[j]-testset.ptr.pp_double[i][nin+j], _state);
                    cvrep->avgerror = cvrep->avgerror+ae_fabs(y.ptr.p_double[j]-testset.ptr.pp_double[i][nin+j], _state);
                    if( ae_fp_neq(testset.ptr.pp_double[i][nin+j],0) )
                    {
                        cvrep->avgrelerror = cvrep->avgrelerror+ae_fabs((y.ptr.p_double[j]-testset.ptr.pp_double[i][nin+j])/testset.ptr.pp_double[i][nin+j], _state);
                        relcnt = relcnt+1;
                    }
                }
            }
        }
    }
    if( mlpissoftmax(&network, _state) )
    {
        cvrep->relclserror = cvrep->relclserror/npoints;
        cvrep->avgce = cvrep->avgce/(ae_log(2, _state)*npoints);
    }
    cvrep->rmserror = ae_sqrt(cvrep->rmserror/(npoints*nout), _state);
    cvrep->avgerror = cvrep->avgerror/(npoints*nout);
    if( relcnt>0 )
    {
        cvrep->avgrelerror = cvrep->avgrelerror/relcnt;
    }
    *info = 1;
    ae_frame_leave(_state);
}


/*************************************************************************
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.
*************************************************************************/
static void mlptrain_mlpkfoldsplit(/* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     ae_int_t nclasses,
     ae_int_t foldscount,
     ae_bool stratifiedsplits,
     /* Integer */ ae_vector* folds,
     ae_state *_state)
{
    ae_int_t i;
    ae_int_t j;
    ae_int_t k;

    ae_vector_clear(folds);

    
    /*
     * test parameters
     */
    ae_assert(npoints>0, "MLPKFoldSplit: wrong NPoints!", _state);
    ae_assert(nclasses>1||nclasses<0, "MLPKFoldSplit: wrong NClasses!", _state);
    ae_assert(foldscount>=2&&foldscount<=npoints, "MLPKFoldSplit: wrong FoldsCount!", _state);
    ae_assert(!stratifiedsplits, "MLPKFoldSplit: stratified splits are not supported!", _state);
    
    /*
     * Folds
     */
    ae_vector_set_length(folds, npoints-1+1, _state);
    for(i=0; i<=npoints-1; i++)
    {
        folds->ptr.p_int[i] = i*foldscount/npoints;
    }
    for(i=0; i<=npoints-2; i++)
    {
        j = i+ae_randominteger(npoints-i, _state);
        if( j!=i )
        {
            k = folds->ptr.p_int[i];
            folds->ptr.p_int[i] = folds->ptr.p_int[j];
            folds->ptr.p_int[j] = k;
        }
    }
}


static void mlptrain_mthreadcv(mlptrainer* s,
     ae_int_t rowsize,
     ae_int_t nrestarts,
     /* Integer */ ae_vector* folds,
     ae_int_t fold,
     ae_int_t dfold,
     /* Real    */ ae_matrix* cvy,
     ae_shared_pool* pooldatacv,
     ae_state *_state)
{
    ae_frame _frame_block;
    mlpparallelizationcv *datacv;
    ae_smart_ptr _datacv;
    ae_int_t i;

    ae_frame_make(_state, &_frame_block);
    ae_smart_ptr_init(&_datacv, (void**)&datacv, _state, ae_true);

    if( fold==dfold-1 )
    {
        
        /*
         * Separate set
         */
        ae_shared_pool_retrieve(pooldatacv, &_datacv, _state);
        datacv->subsetsize = 0;
        for(i=0; i<=s->npoints-1; i++)
        {
            if( folds->ptr.p_int[i]!=fold )
            {
                datacv->subset.ptr.p_int[datacv->subsetsize] = i;
                datacv->subsetsize = datacv->subsetsize+1;
            }
        }
        
        /*
         * Train on CV training set
         */
        mlptrain_mlptrainnetworkx(s, &datacv->network, &datacv->tnetwork, &datacv->state, nrestarts, &datacv->subset, datacv->subsetsize, &datacv->subset, 0, &datacv->bufwbest, &datacv->bufwfinal, &datacv->rep, _state);
        datacv->ngrad = datacv->ngrad+datacv->rep.ngrad;
        
        /*
         * Estimate error using CV test set
         */
        for(i=0; i<=s->npoints-1; i++)
        {
            if( folds->ptr.p_int[i]==fold )
            {
                if( s->datatype==0 )
                {
                    ae_v_move(&datacv->xyrow.ptr.p_double[0], 1, &s->densexy.ptr.pp_double[i][0], 1, ae_v_len(0,rowsize-1));
                }
                if( s->datatype==1 )
                {
                    sparsegetrow(&s->sparsexy, i, &datacv->xyrow, _state);
                }
                mlpprocess(&datacv->network, &datacv->xyrow, &datacv->y, _state);
                ae_v_move(&cvy->ptr.pp_double[i][0], 1, &datacv->y.ptr.p_double[0], 1, ae_v_len(0,s->nout-1));
            }
        }
        ae_shared_pool_recycle(pooldatacv, &_datacv, _state);
    }
    else
    {
        ae_assert(fold<dfold-1, "MThreadCV: internal error(Fold>DFold-1).", _state);
        mlptrain_mthreadcv(s, rowsize, nrestarts, folds, fold, (fold+dfold)/2, cvy, pooldatacv, _state);
        mlptrain_mthreadcv(s, rowsize, nrestarts, folds, (fold+dfold)/2, dfold, cvy, pooldatacv, _state);
    }
    ae_frame_leave(_state);
}


/*************************************************************************
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;
    TNetwork    -   the training neural network.
                    User  may  look  weights  in  parameter Network  while
                    continue training process.
                    It has architecture like Network. You have to  copy or 
                    create new network with architecture like Network.
    State       -   created LBFGS optimizer;
    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.
    TrnSubset   -   some subset from training set(it stores row's numbers),
                    used as trainig set;
   TrnSubsetSize-   size of subset(if TrnSubsetSize<0 - used full dataset);
                    when TrnSubsetSize=0, network is filled by zero value,
                    and ValSubset parameter is IGNORED;
    ValSubset   -   some subset from training set(it stores row's numbers),
                    used as validation set;
   ValSubsetSize-   size of subset(if ValSubsetSize<0 - used full dataset);
                    when  ValSubsetSize<>0  this  mean  that is used early
                    stopping training algorithm;
    BufWBest    -   buffer for storing interim resuls (BufWBest[0:WCOunt-1]
                    it has be allocated by user);
    BufWFinal   -   buffer for storing interim resuls(BufWFinal[0:WCOunt-1]
                    it has be allocated by user).

OUTPUT PARAMETERS:
    Network     -   trained network;
    Rep         -   training report.

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.

  -- ALGLIB --
     Copyright 13.08.2012 by Bochkanov Sergey
*************************************************************************/
static void mlptrain_mlptrainnetworkx(mlptrainer* s,
     multilayerperceptron* network,
     multilayerperceptron* tnetwork,
     minlbfgsstate* state,
     ae_int_t nrestarts,
     /* Integer */ ae_vector* trnsubset,
     ae_int_t trnsubsetsize,
     /* Integer */ ae_vector* valsubset,
     ae_int_t valsubsetsize,
     /* Real    */ ae_vector* bufwbest,
     /* Real    */ ae_vector* bufwfinal,
     mlpreport* rep,
     ae_state *_state)
{
    ae_frame _frame_block;
    modelerrors modrep;
    double eval;
    double v;
    double ebestcur;
    double efinal;
    ae_int_t ngradbatch;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    ae_int_t twcount;
    ae_int_t itbest;
    ae_int_t itcnt;
    ae_int_t ntype;
    ae_int_t ttype;
    ae_bool rndstart;
    ae_int_t pass;
    ae_int_t i;

    ae_frame_make(_state, &_frame_block);
    _mlpreport_clear(rep);
    _modelerrors_init(&modrep, _state, ae_true);

    ae_assert(s->npoints>=0, "MLPTrainNetworkX: internal error - parameter S is not initialized or is spoiled(S.NPoints<0)", _state);
    if( s->rcpar )
    {
        ttype = 0;
    }
    else
    {
        ttype = 1;
    }
    if( !mlpissoftmax(network, _state) )
    {
        ntype = 0;
    }
    else
    {
        ntype = 1;
    }
    ae_assert(ntype==ttype, "MLPTrainNetworkX: internal error - type of the resulting network is not similar to network type in trainer object", _state);
    if( !mlpissoftmax(tnetwork, _state) )
    {
        ntype = 0;
    }
    else
    {
        ntype = 1;
    }
    ae_assert(ntype==ttype, "MLPTrainNetworkX: internal error - type of the training network is not similar to network type in trainer object", _state);
    mlpproperties(network, &nin, &nout, &wcount, _state);
    ae_assert(s->nin==nin, "MLPTrainNetworkX: internal error - number of inputs in trainer is not equal to number of inputs in the network.", _state);
    ae_assert(s->nout==nout, "MLPTrainNetworkX: internal error - number of outputs in trainer is not equal to number of outputs in the network.", _state);
    mlpproperties(tnetwork, &nin, &nout, &twcount, _state);
    ae_assert(s->nin==nin, "MLPTrainNetworkX: internal error - number of inputs in trainer is not equal to number of inputs in the training network.", _state);
    ae_assert(s->nout==nout, "MLPTrainNetworkX: internal error - number of outputs in trainer is not equal to number of outputs in the training network.", _state);
    ae_assert(twcount==wcount, "MLPTrainNetworkX: internal error - number of weights the resulting network is not equal to number of weights in the training network.", _state);
    ae_assert(nrestarts>=0, "MLPTrainNetworkX: internal error - NRestarts<0.", _state);
    ae_assert(trnsubset->cnt>=trnsubsetsize, "MLPTrainNetworkX: internal error - parameter TrnSubsetSize more than input subset size(Length(TrnSubset)<TrnSubsetSize)", _state);
    for(i=0; i<=trnsubsetsize-1; i++)
    {
        ae_assert(trnsubset->ptr.p_int[i]>=0&&trnsubset->ptr.p_int[i]<=s->npoints-1, "MLPTrainNetworkX: internal error - parameter TrnSubset contains incorrect index(TrnSubset[I]<0 or TrnSubset[I]>S.NPoints-1)", _state);
    }
    ae_assert(valsubset->cnt>=valsubsetsize, "MLPTrainNetworkX: internal error - parameter ValSubsetSize more than input subset size(Length(ValSubset)<ValSubsetSize)", _state);
    for(i=0; i<=valsubsetsize-1; i++)
    {
        ae_assert(valsubset->ptr.p_int[i]>=0&&valsubset->ptr.p_int[i]<=s->npoints-1, "MLPTrainNetworkX: internal error - parameter ValSubset contains incorrect index(ValSubset[I]<0 or ValSubset[I]>S.NPoints-1)", _state);
    }
    
    /*
     * Initialize parameter Rep
     */
    rep->relclserror = 0;
    rep->avgce = 0;
    rep->rmserror = 0;
    rep->avgerror = 0;
    rep->avgrelerror = 0;
    rep->ngrad = 0;
    rep->nhess = 0;
    rep->ncholesky = 0;
    if( ((s->datatype==0||s->datatype==1)&&s->npoints>0)&&trnsubsetsize!=0 )
    {
        
        /*
         * Prepare
         */
        efinal = ae_maxrealnumber;
        if( nrestarts!=0 )
        {
            rndstart = ae_true;
        }
        else
        {
            rndstart = ae_false;
            nrestarts = 1;
        }
        ngradbatch = 0;
        eval = 0;
        ebestcur = 0;
        for(pass=1; pass<=nrestarts; pass++)
        {
            mlptrain_mlpstarttrainingx(s, network, tnetwork, state, rndstart, trnsubset, trnsubsetsize, _state);
            itbest = 0;
            itcnt = 0;
            if( s->datatype==0 )
            {
                ebestcur = mlperrorsubset(network, &s->densexy, s->npoints, valsubset, valsubsetsize, _state);
            }
            if( s->datatype==1 )
            {
                ebestcur = mlperrorsparsesubset(network, &s->sparsexy, s->npoints, valsubset, valsubsetsize, _state);
            }
            ae_v_move(&bufwbest->ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
            while(mlptrain_mlpcontinuetrainingx(s, network, tnetwork, state, trnsubset, trnsubsetsize, &ngradbatch, _state))
            {
                if( s->datatype==0 )
                {
                    eval = mlperrorsubset(network, &s->densexy, s->npoints, valsubset, valsubsetsize, _state);
                }
                if( s->datatype==1 )
                {
                    eval = mlperrorsparsesubset(network, &s->sparsexy, s->npoints, valsubset, valsubsetsize, _state);
                }
                if( ae_fp_less_eq(eval,ebestcur) )
                {
                    ae_v_move(&bufwbest->ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
                    ebestcur = eval;
                    itbest = itcnt;
                }
                if( itcnt>30&&ae_fp_greater(itcnt,1.5*itbest) )
                {
                    break;
                }
                itcnt = itcnt+1;
            }
            ae_v_move(&network->weights.ptr.p_double[0], 1, &bufwbest->ptr.p_double[0], 1, ae_v_len(0,wcount-1));
            
            /*
             * Compare with final(the best) answer.
             */
            v = ae_v_dotproduct(&bufwbest->ptr.p_double[0], 1, &bufwbest->ptr.p_double[0], 1, ae_v_len(0,wcount-1));
            if( s->datatype==0 )
            {
                ebestcur = mlperrorsubset(network, &s->densexy, s->npoints, trnsubset, trnsubsetsize, _state)+0.5*s->decay*v;
            }
            if( s->datatype==1 )
            {
                ebestcur = mlperrorsparsesubset(network, &s->sparsexy, s->npoints, trnsubset, trnsubsetsize, _state)+0.5*s->decay*v;
            }
            if( ae_fp_less(ebestcur,efinal) )
            {
                ae_v_move(&bufwfinal->ptr.p_double[0], 1, &bufwbest->ptr.p_double[0], 1, ae_v_len(0,wcount-1));
                efinal = ebestcur;
            }
        }
        
        /*
         * Final network
         */
        ae_v_move(&network->weights.ptr.p_double[0], 1, &bufwfinal->ptr.p_double[0], 1, ae_v_len(0,wcount-1));
        rep->ngrad = ngradbatch;
    }
    else
    {
        for(i=0; i<=wcount-1; i++)
        {
            network->weights.ptr.p_double[i] = 0;
        }
    }
    
    /*
     * Calculate errors.
     */
    if( s->datatype==0 )
    {
        mlpallerrorssubset(network, &s->densexy, s->npoints, trnsubset, trnsubsetsize, &modrep, _state);
    }
    if( s->datatype==1 )
    {
        mlpallerrorssparsesubset(network, &s->sparsexy, s->npoints, trnsubset, trnsubsetsize, &modrep, _state);
    }
    rep->relclserror = modrep.relclserror;
    rep->avgce = modrep.avgce;
    rep->rmserror = modrep.rmserror;
    rep->avgerror = modrep.avgerror;
    rep->avgrelerror = modrep.avgrelerror;
    ae_frame_leave(_state);
}


/*************************************************************************
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.

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 which receives A  COPY  of  the  actual
                    network which is trained by the algorithm. After  each
                    training roung state of the network being  trained  is
                    copied to this variable.
                    It must have same number of inputs and  output/classes
                    as was specified during creation of the trainer object
                    and  it  must  have  exactly  same architecture as the
                    second network (TNetwork).
    TNetwork    -   neural network being trained.
    State       -   LBFGS  optimizer,  already  initialized,   number   of
                    dimensions  must  be equal to number of weights in the
                    networks.
    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.
    Subset      -   some subset from training set(it stores row's numbers);
    SubsetSize  -   size of subset(if SubsetSize<0 - used full dataset).
                    
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.

  -- ALGLIB --
     Copyright 13.08.2012 by Bochkanov Sergey
*************************************************************************/
static void mlptrain_mlpstarttrainingx(mlptrainer* s,
     multilayerperceptron* network,
     multilayerperceptron* tnetwork,
     minlbfgsstate* state,
     ae_bool randomstart,
     /* Integer */ ae_vector* subset,
     ae_int_t subsetsize,
     ae_state *_state)
{
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    ae_int_t twcount;
    ae_int_t ntype;
    ae_int_t ttype;
    ae_int_t i;


    ae_assert(s->npoints>=0, "MLPStartTrainingX: internal error - parameter S is not initialized or is spoiled(S.NPoints<0)", _state);
    if( s->rcpar )
    {
        ttype = 0;
    }
    else
    {
        ttype = 1;
    }
    if( !mlpissoftmax(network, _state) )
    {
        ntype = 0;
    }
    else
    {
        ntype = 1;
    }
    ae_assert(ntype==ttype, "MLPStartTrainingX: internal error - type of the resulting network is not similar to network type in trainer object", _state);
    if( !mlpissoftmax(tnetwork, _state) )
    {
        ntype = 0;
    }
    else
    {
        ntype = 1;
    }
    ae_assert(ntype==ttype, "MLPStartTrainingX: internal error - type of the training network is not similar to network type in trainer object", _state);
    mlpproperties(network, &nin, &nout, &wcount, _state);
    ae_assert(s->nin==nin, "MLPStartTrainingX: number of inputs in trainer is not equal to number of inputs in the network.", _state);
    ae_assert(s->nout==nout, "MLPStartTrainingX: number of outputs in trainer is not equal to number of outputs in the network.", _state);
    mlpproperties(tnetwork, &nin, &nout, &twcount, _state);
    ae_assert(s->nin==nin, "MLPStartTrainingX: number of inputs in trainer is not equal to number of inputs in the training network.", _state);
    ae_assert(s->nout==nout, "MLPStartTrainingX: number of outputs in trainer is not equal to number of outputs in the training network.", _state);
    ae_assert(twcount==wcount, "MLPStartTrainingX: number of weights the resulting network is not equal to number of weights in the training network.", _state);
    ae_assert(subset->cnt>=subsetsize, "MLPStartTrainingX: internal error - parameter SubsetSize more than input subset size(Length(Subset)<SubsetSize)", _state);
    for(i=0; i<=subsetsize-1; i++)
    {
        ae_assert(subset->ptr.p_int[i]>=0&&subset->ptr.p_int[i]<=s->npoints-1, "MLPStartTrainingX: internal error - parameter Subset contains incorrect index(Subset[I]<0 or Subset[I]>S.NPoints-1)", _state);
    }
    if( ((s->datatype==0||s->datatype==1)&&s->npoints>0)&&subsetsize!=0 )
    {
        
        /*
         * Prepare
         */
        if( s->datatype==0 )
        {
            mlpinitpreprocessorsubset(network, &s->densexy, s->npoints, subset, subsetsize, _state);
            mlpinitpreprocessorsubset(tnetwork, &s->densexy, s->npoints, subset, subsetsize, _state);
        }
        if( s->datatype==1 )
        {
            mlpinitpreprocessorsparsesubset(network, &s->sparsexy, s->npoints, subset, subsetsize, _state);
            mlpinitpreprocessorsparsesubset(tnetwork, &s->sparsexy, s->npoints, subset, subsetsize, _state);
        }
        
        /*
         * Process
         */
        if( randomstart )
        {
            mlprandomize(network, _state);
        }
        minlbfgsrestartfrom(state, &network->weights, _state);
    }
    else
    {
        for(i=0; i<=wcount-1; i++)
        {
            network->weights.ptr.p_double[i] = 0;
        }
    }
    
    /*
     * Copy weights
     */
    ae_v_move(&tnetwork->weights.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-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]
    >

INPUT PARAMETERS:
    S           -   trainer object
    Network     -   neural network which receives A  COPY  of  the  actual
                    network which is trained by the algorithm. After  each
                    training roung state of the network being  trained  is
                    copied to this variable.
                    It must have same number of inputs and  output/classes
                    as was specified during creation of the trainer object
                    and  it  must  have  exactly  same architecture as the
                    second network (TNetwork).
    TNetwork    -   neural network being trained.
    State       -   LBFGS  optimizer,  already  initialized,   number   of
                    dimensions  must  be equal to number of weights in the
                    networks.
    Subset      -   some subset from training set(it stores row's numbers);
    SubsetSize  -   size of subset(if SubsetSize<0 - used full dataset).
    NGradBatch  -   number  of calls  MLPGradBatch function.  Initial value
                    is zero;
    
OUTPUT PARAMETERS:
    Network     -   weights of the neural network  are  rewritten  by  the
                    current approximation;
    NGradBatch  -   number  of calls  MLPGradBatch function after training.

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.
      

  -- ALGLIB --
     Copyright 13.08.2012 by Bochkanov Sergey
*************************************************************************/
static ae_bool mlptrain_mlpcontinuetrainingx(mlptrainer* s,
     multilayerperceptron* network,
     multilayerperceptron* tnetwork,
     minlbfgsstate* state,
     /* Integer */ ae_vector* subset,
     ae_int_t subsetsize,
     ae_int_t* ngradbatch,
     ae_state *_state)
{
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;
    ae_int_t twcount;
    ae_int_t ntype;
    ae_int_t ttype;
    double decay;
    double v;
    ae_int_t i;
    ae_bool result;


    ae_assert(s->npoints>=0, "MLPContinueTrainingX: internal error - parameter S is not initialized or is spoiled(S.NPoints<0).", _state);
    if( s->rcpar )
    {
        ttype = 0;
    }
    else
    {
        ttype = 1;
    }
    if( !mlpissoftmax(network, _state) )
    {
        ntype = 0;
    }
    else
    {
        ntype = 1;
    }
    ae_assert(ntype==ttype, "MLPContinueTrainingX: internal error - type of the resulting network is not similar to network type in trainer object.", _state);
    if( !mlpissoftmax(tnetwork, _state) )
    {
        ntype = 0;
    }
    else
    {
        ntype = 1;
    }
    ae_assert(ntype==ttype, "MLPContinueTrainingX: internal error - type of the training network is not similar to network type in trainer object.", _state);
    mlpproperties(network, &nin, &nout, &wcount, _state);
    ae_assert(s->nin==nin, "MLPContinueTrainingX: internal error - number of inputs in trainer is not equal to number of inputs in the network.", _state);
    ae_assert(s->nout==nout, "MLPContinueTrainingX: internal error - number of outputs in trainer is not equal to number of outputs in the network.", _state);
    mlpproperties(tnetwork, &nin, &nout, &twcount, _state);
    ae_assert(s->nin==nin, "MLPContinueTrainingX: internal error - number of inputs in trainer is not equal to number of inputs in the training network.", _state);
    ae_assert(s->nout==nout, "MLPContinueTrainingX: internal error - number of outputs in trainer is not equal to number of outputs in the training network.", _state);
    ae_assert(twcount==wcount, "MLPContinueTrainingX: internal error - number of weights the resulting network is not equal to number of weights in the training network.", _state);
    ae_assert(subset->cnt>=subsetsize, "MLPContinueTrainingX: internal error - parameter SubsetSize more than input subset size(Length(Subset)<SubsetSize).", _state);
    for(i=0; i<=subsetsize-1; i++)
    {
        ae_assert(subset->ptr.p_int[i]>=0&&subset->ptr.p_int[i]<=s->npoints-1, "MLPContinueTrainingX: internal error - parameter Subset contains incorrect index(Subset[I]<0 or Subset[I]>S.NPoints-1).", _state);
    }
    if( ((s->datatype==0||s->datatype==1)&&s->npoints>0)&&subsetsize!=0 )
    {
        decay = s->decay;
        while(minlbfgsiteration(state, _state))
        {
            if( state->xupdated )
            {
                ae_v_move(&network->weights.ptr.p_double[0], 1, &tnetwork->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
                result = ae_true;
                return result;
            }
            ae_v_move(&tnetwork->weights.ptr.p_double[0], 1, &state->x.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
            if( s->datatype==0 )
            {
                mlpgradbatchsubset(tnetwork, &s->densexy, s->npoints, subset, subsetsize, &state->f, &state->g, _state);
            }
            if( s->datatype==1 )
            {
                mlpgradbatchsparsesubset(tnetwork, &s->sparsexy, s->npoints, subset, subsetsize, &state->f, &state->g, _state);
            }
            
            /*
             * Increment number of operations performed on batch gradient
             */
            *ngradbatch = *ngradbatch+1;
            v = ae_v_dotproduct(&tnetwork->weights.ptr.p_double[0], 1, &tnetwork->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
            state->f = state->f+0.5*decay*v;
            ae_v_addd(&state->g.ptr.p_double[0], 1, &tnetwork->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1), decay);
        }
        ae_v_move(&network->weights.ptr.p_double[0], 1, &tnetwork->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1));
    }
    result = ae_false;
    return result;
}


/*************************************************************************
Internal bagging subroutine.

  -- ALGLIB --
     Copyright 19.02.2009 by Bochkanov Sergey
*************************************************************************/
static void mlptrain_mlpebagginginternal(mlpensemble* ensemble,
     /* Real    */ ae_matrix* xy,
     ae_int_t npoints,
     double decay,
     ae_int_t restarts,
     double wstep,
     ae_int_t maxits,
     ae_bool lmalgorithm,
     ae_int_t* info,
     mlpreport* rep,
     mlpcvreport* ooberrors,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_matrix xys;
    ae_vector s;
    ae_matrix oobbuf;
    ae_vector oobcntbuf;
    ae_vector x;
    ae_vector y;
    ae_vector dy;
    ae_vector dsbuf;
    ae_int_t ccnt;
    ae_int_t pcnt;
    ae_int_t i;
    ae_int_t j;
    ae_int_t k;
    double v;
    mlpreport tmprep;
    ae_int_t nin;
    ae_int_t nout;
    ae_int_t wcount;

    ae_frame_make(_state, &_frame_block);
    *info = 0;
    _mlpreport_clear(rep);
    _mlpcvreport_clear(ooberrors);
    ae_matrix_init(&xys, 0, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&s, 0, DT_BOOL, _state, ae_true);
    ae_matrix_init(&oobbuf, 0, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&oobcntbuf, 0, DT_INT, _state, ae_true);
    ae_vector_init(&x, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&y, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&dy, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&dsbuf, 0, DT_REAL, _state, ae_true);
    _mlpreport_init(&tmprep, _state, ae_true);

    nin = mlpgetinputscount(&ensemble->network, _state);
    nout = mlpgetoutputscount(&ensemble->network, _state);
    wcount = mlpgetweightscount(&ensemble->network, _state);
    
    /*
     * Test for inputs
     */
    if( (!lmalgorithm&&ae_fp_eq(wstep,0))&&maxits==0 )
    {
        *info = -8;
        ae_frame_leave(_state);
        return;
    }
    if( ((npoints<=0||restarts<1)||ae_fp_less(wstep,0))||maxits<0 )
    {
        *info = -1;
        ae_frame_leave(_state);
        return;
    }
    if( mlpissoftmax(&ensemble->network, _state) )
    {
        for(i=0; i<=npoints-1; i++)
        {
            if( ae_round(xy->ptr.pp_double[i][nin], _state)<0||ae_round(xy->ptr.pp_double[i][nin], _state)>=nout )
            {
                *info = -2;
                ae_frame_leave(_state);
                return;
            }
        }
    }
    
    /*
     * allocate temporaries
     */
    *info = 2;
    rep->ngrad = 0;
    rep->nhess = 0;
    rep->ncholesky = 0;
    ooberrors->relclserror = 0;
    ooberrors->avgce = 0;
    ooberrors->rmserror = 0;
    ooberrors->avgerror = 0;
    ooberrors->avgrelerror = 0;
    if( mlpissoftmax(&ensemble->network, _state) )
    {
        ccnt = nin+1;
        pcnt = nin;
    }
    else
    {
        ccnt = nin+nout;
        pcnt = nin+nout;
    }
    ae_matrix_set_length(&xys, npoints, ccnt, _state);
    ae_vector_set_length(&s, npoints, _state);
    ae_matrix_set_length(&oobbuf, npoints, nout, _state);
    ae_vector_set_length(&oobcntbuf, npoints, _state);
    ae_vector_set_length(&x, nin, _state);
    ae_vector_set_length(&y, nout, _state);
    if( mlpissoftmax(&ensemble->network, _state) )
    {
        ae_vector_set_length(&dy, 1, _state);
    }
    else
    {
        ae_vector_set_length(&dy, nout, _state);
    }
    for(i=0; i<=npoints-1; i++)
    {
        for(j=0; j<=nout-1; j++)
        {
            oobbuf.ptr.pp_double[i][j] = 0;
        }
    }
    for(i=0; i<=npoints-1; i++)
    {
        oobcntbuf.ptr.p_int[i] = 0;
    }
    
    /*
     * main bagging cycle
     */
    for(k=0; k<=ensemble->ensemblesize-1; k++)
    {
        
        /*
         * prepare dataset
         */
        for(i=0; i<=npoints-1; i++)
        {
            s.ptr.p_bool[i] = ae_false;
        }
        for(i=0; i<=npoints-1; i++)
        {
            j = ae_randominteger(npoints, _state);
            s.ptr.p_bool[j] = ae_true;
            ae_v_move(&xys.ptr.pp_double[i][0], 1, &xy->ptr.pp_double[j][0], 1, ae_v_len(0,ccnt-1));
        }
        
        /*
         * train
         */
        if( lmalgorithm )
        {
            mlptrainlm(&ensemble->network, &xys, npoints, decay, restarts, info, &tmprep, _state);
        }
        else
        {
            mlptrainlbfgs(&ensemble->network, &xys, npoints, decay, restarts, wstep, maxits, info, &tmprep, _state);
        }
        if( *info<0 )
        {
            ae_frame_leave(_state);
            return;
        }
        
        /*
         * save results
         */
        rep->ngrad = rep->ngrad+tmprep.ngrad;
        rep->nhess = rep->nhess+tmprep.nhess;
        rep->ncholesky = rep->ncholesky+tmprep.ncholesky;
        ae_v_move(&ensemble->weights.ptr.p_double[k*wcount], 1, &ensemble->network.weights.ptr.p_double[0], 1, ae_v_len(k*wcount,(k+1)*wcount-1));
        ae_v_move(&ensemble->columnmeans.ptr.p_double[k*pcnt], 1, &ensemble->network.columnmeans.ptr.p_double[0], 1, ae_v_len(k*pcnt,(k+1)*pcnt-1));
        ae_v_move(&ensemble->columnsigmas.ptr.p_double[k*pcnt], 1, &ensemble->network.columnsigmas.ptr.p_double[0], 1, ae_v_len(k*pcnt,(k+1)*pcnt-1));
        
        /*
         * OOB estimates
         */
        for(i=0; i<=npoints-1; i++)
        {
            if( !s.ptr.p_bool[i] )
            {
                ae_v_move(&x.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nin-1));
                mlpprocess(&ensemble->network, &x, &y, _state);
                ae_v_add(&oobbuf.ptr.pp_double[i][0], 1, &y.ptr.p_double[0], 1, ae_v_len(0,nout-1));
                oobcntbuf.ptr.p_int[i] = oobcntbuf.ptr.p_int[i]+1;
            }
        }
    }
    
    /*
     * OOB estimates
     */
    if( mlpissoftmax(&ensemble->network, _state) )
    {
        dserrallocate(nout, &dsbuf, _state);
    }
    else
    {
        dserrallocate(-nout, &dsbuf, _state);
    }
    for(i=0; i<=npoints-1; i++)
    {
        if( oobcntbuf.ptr.p_int[i]!=0 )
        {
            v = (double)1/(double)oobcntbuf.ptr.p_int[i];
            ae_v_moved(&y.ptr.p_double[0], 1, &oobbuf.ptr.pp_double[i][0], 1, ae_v_len(0,nout-1), v);
            if( mlpissoftmax(&ensemble->network, _state) )
            {
                dy.ptr.p_double[0] = xy->ptr.pp_double[i][nin];
            }
            else
            {
                ae_v_moved(&dy.ptr.p_double[0], 1, &xy->ptr.pp_double[i][nin], 1, ae_v_len(0,nout-1), v);
            }
            dserraccumulate(&dsbuf, &y, &dy, _state);
        }
    }
    dserrfinish(&dsbuf, _state);
    ooberrors->relclserror = dsbuf.ptr.p_double[0];
    ooberrors->avgce = dsbuf.ptr.p_double[1];
    ooberrors->rmserror = dsbuf.ptr.p_double[2];
    ooberrors->avgerror = dsbuf.ptr.p_double[3];
    ooberrors->avgrelerror = dsbuf.ptr.p_double[4];
    ae_frame_leave(_state);
}


ae_bool _mlpreport_init(void* _p, ae_state *_state, ae_bool make_automatic)
{
    mlpreport *p = (mlpreport*)_p;
    ae_touch_ptr((void*)p);
    return ae_true;
}


ae_bool _mlpreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic)
{
    mlpreport *dst = (mlpreport*)_dst;
    mlpreport *src = (mlpreport*)_src;
    dst->relclserror = src->relclserror;
    dst->avgce = src->avgce;
    dst->rmserror = src->rmserror;
    dst->avgerror = src->avgerror;
    dst->avgrelerror = src->avgrelerror;
    dst->ngrad = src->ngrad;
    dst->nhess = src->nhess;
    dst->ncholesky = src->ncholesky;
    return ae_true;
}


void _mlpreport_clear(void* _p)
{
    mlpreport *p = (mlpreport*)_p;
    ae_touch_ptr((void*)p);
}


void _mlpreport_destroy(void* _p)
{
    mlpreport *p = (mlpreport*)_p;
    ae_touch_ptr((void*)p);
}


ae_bool _mlpcvreport_init(void* _p, ae_state *_state, ae_bool make_automatic)
{
    mlpcvreport *p = (mlpcvreport*)_p;
    ae_touch_ptr((void*)p);
    return ae_true;
}


ae_bool _mlpcvreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic)
{
    mlpcvreport *dst = (mlpcvreport*)_dst;
    mlpcvreport *src = (mlpcvreport*)_src;
    dst->relclserror = src->relclserror;
    dst->avgce = src->avgce;
    dst->rmserror = src->rmserror;
    dst->avgerror = src->avgerror;
    dst->avgrelerror = src->avgrelerror;
    return ae_true;
}


void _mlpcvreport_clear(void* _p)
{
    mlpcvreport *p = (mlpcvreport*)_p;
    ae_touch_ptr((void*)p);
}


void _mlpcvreport_destroy(void* _p)
{
    mlpcvreport *p = (mlpcvreport*)_p;
    ae_touch_ptr((void*)p);
}


ae_bool _mlptrainer_init(void* _p, ae_state *_state, ae_bool make_automatic)
{
    mlptrainer *p = (mlptrainer*)_p;
    ae_touch_ptr((void*)p);
    if( !ae_matrix_init(&p->densexy, 0, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !_sparsematrix_init(&p->sparsexy, _state, make_automatic) )
        return ae_false;
    if( !_multilayerperceptron_init(&p->tnetwork, _state, make_automatic) )
        return ae_false;
    if( !_minlbfgsstate_init(&p->tstate, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->wbest, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->wfinal, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->subset, 0, DT_INT, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->valsubset, 0, DT_INT, _state, make_automatic) )
        return ae_false;
    return ae_true;
}


ae_bool _mlptrainer_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic)
{
    mlptrainer *dst = (mlptrainer*)_dst;
    mlptrainer *src = (mlptrainer*)_src;
    dst->nin = src->nin;
    dst->nout = src->nout;
    dst->rcpar = src->rcpar;
    dst->lbfgsfactor = src->lbfgsfactor;
    dst->decay = src->decay;
    dst->wstep = src->wstep;
    dst->maxits = src->maxits;
    dst->datatype = src->datatype;
    dst->npoints = src->npoints;
    if( !ae_matrix_init_copy(&dst->densexy, &src->densexy, _state, make_automatic) )
        return ae_false;
    if( !_sparsematrix_init_copy(&dst->sparsexy, &src->sparsexy, _state, make_automatic) )
        return ae_false;
    if( !_multilayerperceptron_init_copy(&dst->tnetwork, &src->tnetwork, _state, make_automatic) )
        return ae_false;
    if( !_minlbfgsstate_init_copy(&dst->tstate, &src->tstate, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->wbest, &src->wbest, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->wfinal, &src->wfinal, _state, make_automatic) )
        return ae_false;
    dst->ngradbatch = src->ngradbatch;
    if( !ae_vector_init_copy(&dst->subset, &src->subset, _state, make_automatic) )
        return ae_false;
    dst->subsetsize = src->subsetsize;
    if( !ae_vector_init_copy(&dst->valsubset, &src->valsubset, _state, make_automatic) )
        return ae_false;
    dst->valsubsetsize = src->valsubsetsize;
    return ae_true;
}


void _mlptrainer_clear(void* _p)
{
    mlptrainer *p = (mlptrainer*)_p;
    ae_touch_ptr((void*)p);
    ae_matrix_clear(&p->densexy);
    _sparsematrix_clear(&p->sparsexy);
    _multilayerperceptron_clear(&p->tnetwork);
    _minlbfgsstate_clear(&p->tstate);
    ae_vector_clear(&p->wbest);
    ae_vector_clear(&p->wfinal);
    ae_vector_clear(&p->subset);
    ae_vector_clear(&p->valsubset);
}


void _mlptrainer_destroy(void* _p)
{
    mlptrainer *p = (mlptrainer*)_p;
    ae_touch_ptr((void*)p);
    ae_matrix_destroy(&p->densexy);
    _sparsematrix_destroy(&p->sparsexy);
    _multilayerperceptron_destroy(&p->tnetwork);
    _minlbfgsstate_destroy(&p->tstate);
    ae_vector_destroy(&p->wbest);
    ae_vector_destroy(&p->wfinal);
    ae_vector_destroy(&p->subset);
    ae_vector_destroy(&p->valsubset);
}


ae_bool _mlpparallelizationcv_init(void* _p, ae_state *_state, ae_bool make_automatic)
{
    mlpparallelizationcv *p = (mlpparallelizationcv*)_p;
    ae_touch_ptr((void*)p);
    if( !_multilayerperceptron_init(&p->network, _state, make_automatic) )
        return ae_false;
    if( !_multilayerperceptron_init(&p->tnetwork, _state, make_automatic) )
        return ae_false;
    if( !_minlbfgsstate_init(&p->state, _state, make_automatic) )
        return ae_false;
    if( !_mlpreport_init(&p->rep, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->subset, 0, DT_INT, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->xyrow, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->y, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->bufwbest, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init(&p->bufwfinal, 0, DT_REAL, _state, make_automatic) )
        return ae_false;
    return ae_true;
}


ae_bool _mlpparallelizationcv_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic)
{
    mlpparallelizationcv *dst = (mlpparallelizationcv*)_dst;
    mlpparallelizationcv *src = (mlpparallelizationcv*)_src;
    if( !_multilayerperceptron_init_copy(&dst->network, &src->network, _state, make_automatic) )
        return ae_false;
    if( !_multilayerperceptron_init_copy(&dst->tnetwork, &src->tnetwork, _state, make_automatic) )
        return ae_false;
    if( !_minlbfgsstate_init_copy(&dst->state, &src->state, _state, make_automatic) )
        return ae_false;
    if( !_mlpreport_init_copy(&dst->rep, &src->rep, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->subset, &src->subset, _state, make_automatic) )
        return ae_false;
    dst->subsetsize = src->subsetsize;
    if( !ae_vector_init_copy(&dst->xyrow, &src->xyrow, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->y, &src->y, _state, make_automatic) )
        return ae_false;
    dst->ngrad = src->ngrad;
    if( !ae_vector_init_copy(&dst->bufwbest, &src->bufwbest, _state, make_automatic) )
        return ae_false;
    if( !ae_vector_init_copy(&dst->bufwfinal, &src->bufwfinal, _state, make_automatic) )
        return ae_false;
    return ae_true;
}


void _mlpparallelizationcv_clear(void* _p)
{
    mlpparallelizationcv *p = (mlpparallelizationcv*)_p;
    ae_touch_ptr((void*)p);
    _multilayerperceptron_clear(&p->network);
    _multilayerperceptron_clear(&p->tnetwork);
    _minlbfgsstate_clear(&p->state);
    _mlpreport_clear(&p->rep);
    ae_vector_clear(&p->subset);
    ae_vector_clear(&p->xyrow);
    ae_vector_clear(&p->y);
    ae_vector_clear(&p->bufwbest);
    ae_vector_clear(&p->bufwfinal);
}


void _mlpparallelizationcv_destroy(void* _p)
{
    mlpparallelizationcv *p = (mlpparallelizationcv*)_p;
    ae_touch_ptr((void*)p);
    _multilayerperceptron_destroy(&p->network);
    _multilayerperceptron_destroy(&p->tnetwork);
    _minlbfgsstate_destroy(&p->state);
    _mlpreport_destroy(&p->rep);
    ae_vector_destroy(&p->subset);
    ae_vector_destroy(&p->xyrow);
    ae_vector_destroy(&p->y);
    ae_vector_destroy(&p->bufwbest);
    ae_vector_destroy(&p->bufwfinal);
}




/*************************************************************************
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

ÂÛÕÎÄÍÛÅ ÏÀÐÀÌÅÒÐÛ:
    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.

  -- ALGLIB --
     Copyright 25.08.2008 by Bochkanov Sergey
*************************************************************************/
void pcabuildbasis(/* Real    */ ae_matrix* x,
     ae_int_t npoints,
     ae_int_t nvars,
     ae_int_t* info,
     /* Real    */ ae_vector* s2,
     /* Real    */ ae_matrix* v,
     ae_state *_state)
{
    ae_frame _frame_block;
    ae_matrix a;
    ae_matrix u;
    ae_matrix vt;
    ae_vector m;
    ae_vector t;
    ae_int_t i;
    ae_int_t j;
    double mean;
    double variance;
    double skewness;
    double kurtosis;

    ae_frame_make(_state, &_frame_block);
    *info = 0;
    ae_vector_clear(s2);
    ae_matrix_clear(v);
    ae_matrix_init(&a, 0, 0, DT_REAL, _state, ae_true);
    ae_matrix_init(&u, 0, 0, DT_REAL, _state, ae_true);
    ae_matrix_init(&vt, 0, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&m, 0, DT_REAL, _state, ae_true);
    ae_vector_init(&t, 0, DT_REAL, _state, ae_true);

    
    /*
     * Check input data
     */
    if( npoints<0||nvars<1 )
    {
        *info = -1;
        ae_frame_leave(_state);
        return;
    }
    *info = 1;
    
    /*
     * Special case: NPoints=0
     */
    if( npoints==0 )
    {
        ae_vector_set_length(s2, nvars-1+1, _state);
        ae_matrix_set_length(v, nvars-1+1, nvars-1+1, _state);
        for(i=0; i<=nvars-1; i++)
        {
            s2->ptr.p_double[i] = 0;
        }
        for(i=0; i<=nvars-1; i++)
        {
            for(j=0; j<=nvars-1; j++)
            {
                if( i==j )
                {
                    v->ptr.pp_double[i][j] = 1;
                }
                else
                {
                    v->ptr.pp_double[i][j] = 0;
                }
            }
        }
        ae_frame_leave(_state);
        return;
    }
    
    /*
     * Calculate means
     */
    ae_vector_set_length(&m, nvars-1+1, _state);
    ae_vector_set_length(&t, npoints-1+1, _state);
    for(j=0; j<=nvars-1; j++)
    {
        ae_v_move(&t.ptr.p_double[0], 1, &x->ptr.pp_double[0][j], x->stride, ae_v_len(0,npoints-1));
        samplemoments(&t, npoints, &mean, &variance, &skewness, &kurtosis, _state);
        m.ptr.p_double[j] = mean;
    }
    
    /*
     * Center, apply SVD, prepare output
     */
    ae_matrix_set_length(&a, ae_maxint(npoints, nvars, _state)-1+1, nvars-1+1, _state);
    for(i=0; i<=npoints-1; i++)
    {
        ae_v_move(&a.ptr.pp_double[i][0], 1, &x->ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1));
        ae_v_sub(&a.ptr.pp_double[i][0], 1, &m.ptr.p_double[0], 1, ae_v_len(0,nvars-1));
    }
    for(i=npoints; i<=nvars-1; i++)
    {
        for(j=0; j<=nvars-1; j++)
        {
            a.ptr.pp_double[i][j] = 0;
        }
    }
    if( !rmatrixsvd(&a, ae_maxint(npoints, nvars, _state), nvars, 0, 1, 2, s2, &u, &vt, _state) )
    {
        *info = -4;
        ae_frame_leave(_state);
        return;
    }
    if( npoints!=1 )
    {
        for(i=0; i<=nvars-1; i++)
        {
            s2->ptr.p_double[i] = ae_sqr(s2->ptr.p_double[i], _state)/(npoints-1);
        }
    }
    ae_matrix_set_length(v, nvars-1+1, nvars-1+1, _state);
    copyandtranspose(&vt, 0, nvars-1, 0, nvars-1, v, 0, nvars-1, 0, nvars-1, _state);
    ae_frame_leave(_state);
}



}

